concepts: Dynamic trait objects
authorMartin Pitt <martin@piware.de>
Sun, 29 Aug 2021 13:55:13 +0000 (15:55 +0200)
committerMartin Pitt <martin@piware.de>
Sun, 29 Aug 2021 13:55:13 +0000 (15:55 +0200)
concepts/src/lib.rs
concepts/src/main.rs

index b7e5eb8f1df11116f8d94fd31fb1afe29491d087..ca0624142dea4d751e4506184bf84508de7e0696 100644 (file)
@@ -108,3 +108,86 @@ impl Iterator for Counter5 {
         }
     }
 }
+
+pub struct Post {
+    state: Option<Box<dyn State>>,
+    content: String,
+}
+
+impl Post {
+    pub fn new() -> Post {
+        Post {
+            state: Some(Box::new(Draft {})),
+            content: String::new(),
+        }
+    }
+
+    pub fn add_text(&mut self, text: &str) {
+        self.content.push_str(text);
+    }
+
+    pub fn content(&self) -> &str {
+        // as_ref() converts Option<Box<State>> to Option<&Box<State>>
+        // state can never be None, all state transitions return a new one
+        self.state.as_ref().unwrap().content(self)
+    }
+
+    pub fn request_review(&mut self) {
+        if let Some(s) = self.state.take() {
+            self.state = Some(s.request_review());
+        }
+    }
+
+    pub fn approve(&mut self) {
+        if let Some(s) = self.state.take() {
+            self.state = Some(s.approve());
+        }
+    }
+}
+
+trait State {
+    fn request_review(self: Box::<Self>) -> Box<dyn State>;
+    fn approve(self: Box::<Self>) -> Box<dyn State>;
+
+    #[allow(unused_variables)]
+    fn content<'a>(&self, post: &'a Post) -> &'a str {
+        ""
+    }
+}
+
+struct Draft {}
+impl State for Draft {
+    fn request_review(self: Box::<Self>) -> Box<dyn State> {
+        Box::new(PendingReview {})
+    }
+
+    fn approve(self: Box::<Self>) -> Box<dyn State> {
+        self
+    }
+}
+
+struct PendingReview {}
+impl State for PendingReview {
+    fn request_review(self: Box::<Self>) -> Box<dyn State> {
+        self
+    }
+
+    fn approve(self: Box::<Self>) -> Box<dyn State> {
+        Box::new(Published {})
+    }
+}
+
+struct Published {}
+impl State for Published {
+    fn request_review(self: Box::<Self>) -> Box<dyn State> {
+        self
+    }
+
+    fn approve(self: Box::<Self>) -> Box<dyn State> {
+        Box::new(Published {})
+    }
+
+    fn content<'a>(&self, post: &'a Post) -> &'a str {
+        &post.content
+    }
+}
index 94bdb960aa7e79b80d4ad22f082ab2822e6c1fbc..f9eafe95978b7bb802c7f1b8ffd3088cf2c19792 100644 (file)
@@ -247,6 +247,19 @@ fn test_threads() {
     println!("counter: {}", *counter.lock().unwrap());
 }
 
+fn test_dyn_traits() {
+    let text = "I ate a salad for lunch today";
+    let mut post = Post::new();
+    post.add_text(text);
+    assert_eq!("", post.content());
+
+    post.request_review();
+    assert_eq!("", post.content());
+
+    post.approve();
+    assert_eq!(text, post.content());
+}
+
 fn main() {
     test_strings();
     test_vectors();
@@ -256,4 +269,5 @@ fn main() {
     test_closures();
     test_iterators();
     test_threads();
+    test_dyn_traits();
 }