「这是我参与11月更文挑战的第 17 天,活动详情查看:2021最后一次更文挑战」
模板
这个应用程序只包括一个单一的页面,每当状态发生变化时就会被刷新。我们将数据标记放在templates/index.html
,这是一个使用Jinja风格的模板的HTML文件。我们将使用 Tera
来处理Rust中的这个问题。
模板在使用前需要被编译,但这有且只会发生一次。我们可以使用 lazy_static
来确保在第一次访问模板时进行编译,然后在以后的所有访问中重复使用编译的结果,起到懒加载的作用。State
State
接下来,我们需要定义应用程序的状态。这个应用程序围绕着一个Todo类型,它有 name, id, bool,用来跟踪它是否已经完成。
#[derive(Debug, Serialize)]
pub struct Todo {
done: bool,
name: String,
id: Uuid,
}
impl Todo {
fn new(name: &str) -> Self {
Self {
done: false,
name: String::from(name),
id: Uuid::new_v4(),
}
}
}
我们只需要提供一个字符串名称,比如 Todos::new("Task")
,将生成一个新的唯一ID,并将其设置为未完成状态。
存储是相当简单的:
#[derive(Debug, Default)]
struct Todos(Vec<Todo>);
我们需要相关的方法来添加新的todos,删除现有的todos,以及切换已完成的状态:
impl Todos {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, todo: Todo) {
self.0.push(todo);
}
pub fn remove(&mut self, id: Uuid) -> Option<Todo> {
let mut idx = self.0.len();
for (i, todo) in self.0.iter().enumerate() {
if todo.id == id {
idx = i;
}
}
if idx < self.0.len() {
let ret = self.0.remove(idx);
Some(ret)
} else {
None
}
}
pub fn todos(&self) -> &[Todo] {
&self.0
}
pub fn toggle(&mut self, id: Uuid) {
for todo in &mut self.0 {
if todo.id == id {
todo.done = !todo.done;
}
}
}
}
值得注意的是,我们不需要写任何特殊的代码来保证它的线程安全。
我们可以像在单线程、同步环境中那样写,并且相信Rust不会让我们不安全的情况下访问可变变量。这就是在 app()
中被实例化的那几行:
let todos = Todos::new();
let context = Arc::new(RwLock::new(todos));
将这一切包裹在一个Arc中,意味着任何访问这个值的线程都可以得到自己对它的引用,RwLock将允许多个并发的读者或正好是一个写者在同一时间。当锁被释放时,后面的线程将能够取得控制权。