「simple TODO APP in Rust」TODO state

256 阅读2分钟

「这是我参与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将允许多个并发的读者或正好是一个写者在同一时间。当锁被释放时,后面的线程将能够取得控制权。