Skip to content

Commit

Permalink
Convert the todolist example to be threadsafe. (#422)
Browse files Browse the repository at this point in the history
For demonstration purposes we mark the interface as threadsafe,
meaning we need to drop all `&mut self` functions and wrap
`self.items` in a suitable wrapper - `RwLock` in this case.

This is done in support of [ADR-0004](
https://github.com/mozilla/uniffi-rs/blob/main/docs/adr/0004-only-threadsafe-interfaces.md),
which will contain a link to this PR/commit to show an example of the
kind of modifications interfaces  need to become threadsafe.
  • Loading branch information
mhammond authored Apr 1, 2021
1 parent 0706b64 commit 454dfff
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 20 deletions.
55 changes: 35 additions & 20 deletions examples/todolist/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::sync::RwLock;

#[derive(Debug, Clone)]
pub struct TodoEntry {
text: String,
Expand Down Expand Up @@ -33,57 +35,70 @@ fn create_entry_with<S: Into<String>>(item: S) -> Result<TodoEntry> {

type Result<T, E = TodoError> = std::result::Result<T, E>;

// I am a simple Todolist
#[derive(Debug, Clone)]
// A simple Todolist. Our .udl defines us as "Threadsafe", meaning it's up to
// us to ensure we are `Send + Sync` - in practice that means none of our
// functions can take `&mut self` and all elements in the struct must also be
// `Send + Sync` - so we wrap our `Vec` in a RwLock (a Mutex would also work,
// but a RwLock is more appropriate for this use-case, so we use it)
#[derive(Debug)]
pub struct TodoList {
items: Vec<String>,
items: RwLock<Vec<String>>,
}

impl TodoList {
fn new() -> Self {
Self { items: Vec::new() }
Self {
items: RwLock::new(Vec::new()),
}
}

fn add_item<S: Into<String>>(&mut self, item: S) -> Result<()> {
fn add_item<S: Into<String>>(&self, item: S) -> Result<()> {
let item = item.into();
if item == "" {
return Err(TodoError::EmptyString(
"Cannot add empty string as item".to_string(),
));
}
if self.items.contains(&item) {
let mut items = self.items.write().unwrap();
if items.contains(&item) {
return Err(TodoError::DuplicateTodo);
}
self.items.push(item);
items.push(item);
Ok(())
}

fn get_last(&self) -> Result<String> {
self.items.last().cloned().ok_or(TodoError::EmptyTodoList)
let items = self.items.read().unwrap();
items.last().cloned().ok_or(TodoError::EmptyTodoList)
}

fn get_first(&self) -> Result<String> {
self.items.first().cloned().ok_or(TodoError::EmptyTodoList)
let items = self.items.read().unwrap();
items.first().cloned().ok_or(TodoError::EmptyTodoList)
}

fn add_entries(&mut self, entries: Vec<TodoEntry>) {
self.items.extend(entries.into_iter().map(|e| e.text))
fn add_entries(&self, entries: Vec<TodoEntry>) {
let mut items = self.items.write().unwrap();
items.extend(entries.into_iter().map(|e| e.text))
}

fn add_entry(&mut self, entry: TodoEntry) -> Result<()> {
fn add_entry(&self, entry: TodoEntry) -> Result<()> {
self.add_item(entry.text)
}

fn add_items<S: Into<String>>(&mut self, items: Vec<S>) {
self.items.extend(items.into_iter().map(Into::into))
fn add_items<S: Into<String>>(&self, items: Vec<S>) {
let mut my_items = self.items.write().unwrap();
my_items.extend(items.into_iter().map(Into::into))
}

fn get_items(&self) -> Vec<String> {
self.items.clone()
let items = self.items.read().unwrap();
items.clone()
}

fn get_entries(&self) -> Vec<TodoEntry> {
self.items
let items = self.items.read().unwrap();
items
.iter()
.map(|text| TodoEntry { text: text.clone() })
.collect()
Expand All @@ -94,14 +109,14 @@ impl TodoList {
Ok(TodoEntry { text })
}

fn clear_item<S: Into<String>>(&mut self, item: S) -> Result<()> {
fn clear_item<S: Into<String>>(&self, item: S) -> Result<()> {
let item = item.into();
let idx = self
.items
let mut items = self.items.write().unwrap();
let idx = items
.iter()
.position(|s| s == &item)
.ok_or(TodoError::TodoDoesNotExist)?;
self.items.remove(idx);
items.remove(idx);
Ok(())
}
}
Expand Down
1 change: 1 addition & 0 deletions examples/todolist/src/todolist.udl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ enum TodoError {
"TodoDoesNotExist", "EmptyTodoList", "DuplicateTodo", "EmptyString", "DeligatedError"
};

[Threadsafe]
interface TodoList {
constructor();
[Throws=TodoError]
Expand Down

0 comments on commit 454dfff

Please sign in to comment.