From aae3029cd7ecdd7330a3de2f059f21dc331200b8 Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Wed, 3 Aug 2016 20:27:06 -0400 Subject: [PATCH] Add rust tutorials --- rust/hello-server/.gitignore | 1 + rust/hello-server/Cargo.toml | 9 ++ rust/hello-server/src/main.rs | 108 +++++++++++++ rust/hello-server/src/store.rs | 107 +++++++++++++ rust/hello-server/src/template.rs | 74 +++++++++ rust/hello-server/src/todo.rs | 70 +++++++++ rust/hello-server/src/todos.tpl | 65 ++++++++ rust/intro/.gitignore | 1 + rust/intro/Cargo.toml | 6 + rust/intro/src/main.rs | 153 ++++++++++++++++++ rust/todo-list-borrow/.gitignore | 1 + rust/todo-list-borrow/Cargo.toml | 6 + rust/todo-list-borrow/src/main.rs | 126 +++++++++++++++ rust/todo-list-redux/.gitignore | 1 + rust/todo-list-redux/Cargo.toml | 6 + rust/todo-list-redux/src/main.rs | 250 ++++++++++++++++++++++++++++++ rust/todo-list/.gitignore | 1 + rust/todo-list/Cargo.toml | 6 + rust/todo-list/src/main.rs | 99 ++++++++++++ 19 files changed, 1090 insertions(+) create mode 100644 rust/hello-server/.gitignore create mode 100644 rust/hello-server/Cargo.toml create mode 100644 rust/hello-server/src/main.rs create mode 100644 rust/hello-server/src/store.rs create mode 100644 rust/hello-server/src/template.rs create mode 100644 rust/hello-server/src/todo.rs create mode 100644 rust/hello-server/src/todos.tpl create mode 100644 rust/intro/.gitignore create mode 100644 rust/intro/Cargo.toml create mode 100644 rust/intro/src/main.rs create mode 100644 rust/todo-list-borrow/.gitignore create mode 100644 rust/todo-list-borrow/Cargo.toml create mode 100644 rust/todo-list-borrow/src/main.rs create mode 100644 rust/todo-list-redux/.gitignore create mode 100644 rust/todo-list-redux/Cargo.toml create mode 100644 rust/todo-list-redux/src/main.rs create mode 100644 rust/todo-list/.gitignore create mode 100644 rust/todo-list/Cargo.toml create mode 100644 rust/todo-list/src/main.rs diff --git a/rust/hello-server/.gitignore b/rust/hello-server/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/rust/hello-server/.gitignore @@ -0,0 +1 @@ +target diff --git a/rust/hello-server/Cargo.toml b/rust/hello-server/Cargo.toml new file mode 100644 index 0000000..9008bbe --- /dev/null +++ b/rust/hello-server/Cargo.toml @@ -0,0 +1,9 @@ +[package] +authors = ["Tim"] +name = "hello-server" +version = "0.1.0" + +[dependencies] +handlebars = "0.18.1" +nickel = "0.8.1" +rustc-serialize = "0.3.19" diff --git a/rust/hello-server/src/main.rs b/rust/hello-server/src/main.rs new file mode 100644 index 0000000..152410d --- /dev/null +++ b/rust/hello-server/src/main.rs @@ -0,0 +1,108 @@ +#[macro_use] extern crate nickel; +extern crate rustc_serialize; +extern crate handlebars; + +use nickel::{Nickel, HttpRouter, FormBody}; +use std::io; +use std::sync::{Arc, Mutex}; +mod todo; +mod store; +mod template; + +// Lets us type `Add("Todo item".to_string())` instead of `TodoAction::Add("Todo item".to_string()) +use todo::TodoAction::{ Add, Remove, Toggle }; + +// Same with the Action enum and VisibilityFilter, Action::* would work too, but this way we list what we use +use store::Action::{ Todos, Visibility }; +use store::VisibilityFilter::{ ShowActive, ShowAll, ShowCompleted }; +use todo::{Todo}; +use store::{ Store, State, reducer }; +use template::render; + +fn main() { + let mut server = Nickel::new(); + + // Create our todo list store + let mut store = Store::create_store(reducer); + + // Add some todos so we've got something to remember + store.dispatch( Todos( Add("one thing".to_string()) ) ); + store.dispatch( Todos( Add("another thing".to_string()) ) ); + + // Put the store in a container that will let us + // safely use it in multi-threaded environment + let store_container = Arc::new( Mutex::new(store) ); + + // Every clone of our container is counted + // so that when the last clone goes out of scope + // the container can be deallocated + let store = store_container.clone(); + + // At the / path let's just render our current todo list + server.get("/", middleware! { |_req, res| + // We get our store from the container by locking it + // from other threads + let store = store.lock().unwrap(); + + // Render from nickel_mustache takes the + // nickel Result struct, a path to a mustache + // template, and the data to use + return render(res, "./src/todos.tpl", store.get_state()) + // And here the lock is released... + }); + + // Let's clone it again for the next closure + let store = store_container.clone(); + + // This time we look for requests like /toggle/1 + server.get("/:action/:id", middleware! { |_req, res| + // We will dispatch an action on our store so we + // get a mutable reference + let mut store = store.lock().unwrap(); + + // We try to parse the id param to an int, this words for the + // toggle and remove actions + if let Ok(num) = _req.param("id").unwrap().parse::() { + match _req.param("action").unwrap() { + "toggle" => { + store.dispatch( Todos( Toggle(num) ) ) + }, + "remove" => store.dispatch( Todos ( Remove(num) ) ), + _ => (), + } + } else { + // Otherwise look for a show action + match _req.param("action").unwrap() { + "show" => { + match _req.param("id").unwrap() { + "all" => store.dispatch( Visibility(ShowAll) ), + "active" => store.dispatch( Visibility(ShowActive) ), + "completed" => store.dispatch( Visibility(ShowCompleted) ), + _ => (), + } + }, + _ => (), + } + } + + // And render the now updated todo list + return render(res, "./src/todos.tpl", store.get_state()) + }); + + // Let's clone it again for the next closure + let store = store_container.clone(); + + server.post("/*", middleware! { |req, res| + let mut store = store.lock().unwrap(); + let form_body = req.form_body().ok().unwrap(); + if let Some(new_todo) = form_body.get("todo") { + if new_todo.len() > 0 { + store.dispatch( Todos( Add(new_todo.to_string()) ) ); + } + } + + return render(res, "./src/todos.tpl", store.get_state()) + }); + + server.listen("0.0.0.0:3000"); +} \ No newline at end of file diff --git a/rust/hello-server/src/store.rs b/rust/hello-server/src/store.rs new file mode 100644 index 0000000..51f1f45 --- /dev/null +++ b/rust/hello-server/src/store.rs @@ -0,0 +1,107 @@ +use rustc_serialize::json::{self, Json, ToJson}; +use store::Action::{ Visibility }; +use todo::{ Todo, TodoAction, todo_reducer }; + +// Ripping off the canonical Redux todo example we'll add a +// visibility filter to our state in addation to the todos we already had +// This state struct will be the single source of state for our todo list program +#[derive(Clone, Debug, RustcEncodable, RustcDecodable)] +pub struct State { + pub todos: Vec, + pub visibility_filter: VisibilityFilter +} + +// By implementing a struct we are creating something very much like +// a class, we can attach methods to it refering to `&self` or `&mut self` +impl State { + // Can be called with State::default() + pub fn default() -> State { + State { + todos: Vec::new(), + visibility_filter: VisibilityFilter::ShowAll, + } + } +} + +impl ToJson for State { + fn to_json(&self) -> Json { + Json::from_str(&json::encode(&self).unwrap()).unwrap() + } +} + +// Rust has enums, so the enum type can replace the "type" property of Redux objects +// The enums will replace `action_creators` too since Todos(Add("Todo item".to_string())) +// is pretty clear +#[derive(Clone, Debug)] +pub enum Action { + Todos(TodoAction), + Visibility(VisibilityFilter), +} + + +// Our 3 visibility states +#[derive(Clone, Debug, RustcEncodable, RustcDecodable)] +pub enum VisibilityFilter { + ShowActive, + ShowAll, + ShowCompleted, +} + +// Our main reducer, returns a new State with the results of the child-reducers +// No combineReducers is implemented here, so it calls the child reducers +// by function name +pub fn reducer(state: &State, action: Action) -> State { + // Always return a new state + State { + todos: todo_reducer(&state.todos, &action), + visibility_filter: visibility_reducer(&state.visibility_filter, &action), + } +} + +// Very simple reducer since the action will either be a VisibilityFilter, in which +// case we will return that, otherwise just return the incoming state +fn visibility_reducer(state: &VisibilityFilter, action: &Action) -> VisibilityFilter { + match *action { + Visibility(ref vis_action) => vis_action.clone(), + _ => state.clone(), + } +} + +// Redux store implementation +pub struct Store { + state: State, + listeners: Vec, + reducer: fn(&State, Action) -> State, +} + +impl Store { + // Takes a reducer function as the only argument + // To keep it simple, State::default() privides the initial state in this example + pub fn create_store(reducer: fn(&State, Action) -> State) -> Store { + Store { + state: State::default(), + listeners: Vec::new(), + reducer: reducer, + } + } + + // Pushes a listener that will be called for any state change + pub fn subscribe(&mut self, listener: fn(&State)) { + self.listeners.push(listener); + } + + // Simply returns a borrowed reference to the state + #[allow(dead_code)] + pub fn get_state(&self) -> &State { + &self.state + } + + // Called for every new action, calls the reducer to update the state + // and then calls every listener with the new State + pub fn dispatch(&mut self, action: Action) { + self.state = (self.reducer)(&self.state, action); + for listener in &self.listeners { + listener(&self.state); + } + } +} \ No newline at end of file diff --git a/rust/hello-server/src/template.rs b/rust/hello-server/src/template.rs new file mode 100644 index 0000000..76216d7 --- /dev/null +++ b/rust/hello-server/src/template.rs @@ -0,0 +1,74 @@ +use rustc_serialize::json::ToJson; +use nickel::{Response, MiddlewareResult}; +use std::path::Path; +use handlebars::{Handlebars, Renderable, RenderError, RenderContext, Helper, Context}; + +fn filter_todo(c: &Context, h: &Helper, ha: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + let active_filter = c.navigate(".", "visibility_filter").as_string().unwrap(); + let is_completed = c.navigate(rc.get_path(), "completed").as_boolean().unwrap(); + let show_todo: bool = match active_filter { + "ShowAll" => true, + "ShowCompleted" => is_completed, + "ShowActive" => !is_completed, + _ => false + }; + + if show_todo { + h.template().unwrap().render(c, ha, rc) + } else { + Ok(()) + } +} + +fn is_selected_filter(c: &Context, h: &Helper, ha: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + let param = h.param(0).unwrap().value().as_string().unwrap(); + let active_filter = c.navigate(".", "visibility_filter").as_string().unwrap(); + let is_selected: bool = match active_filter { + "ShowAll" => if param == "ShowAll" { true } else { false }, + "ShowCompleted" => if param == "ShowCompleted" { true } else { false }, + "ShowActive" => if param == "ShowActive" { true } else { false }, + _ => false + }; + + if is_selected { + h.template().unwrap().render(c, ha, rc) + } else { + Ok(()) + } +} + +#[allow(unused_variables)] +fn active_count(c: &Context, h: &Helper, ha: &Handlebars, rc: &mut RenderContext) -> Result<(), RenderError> { + let todos = c.navigate(".", "todos").as_array().unwrap(); + let count = todos + .into_iter() + .filter(|todo| { + !todo.find("completed").unwrap().as_boolean().unwrap() && + !todo.find("deleted").unwrap().as_boolean().unwrap() + }) + .count(); + + let mut output = count.to_string(); + + if count == 1 { + output.push_str(" item left"); + } else { + output.push_str(" items left"); + } + + rc.writer.write(output.as_bytes()).unwrap(); + Ok(()) +} + +pub fn render<'mw, T:ToJson>(res: Response<'mw>, path: &str, data: &T) -> MiddlewareResult<'mw> { + let mut handlebars = Handlebars::new(); + + handlebars.register_helper("filter_todo", Box::new(filter_todo)); + handlebars.register_helper("active_count", Box::new(active_count)); + handlebars.register_helper("is_selected_filter", Box::new(is_selected_filter)); + handlebars.register_template_file("template", &Path::new(path)).ok().unwrap(); + + let result = handlebars.render("template", data).ok().unwrap(); + + res.send(result) +} \ No newline at end of file diff --git a/rust/hello-server/src/todo.rs b/rust/hello-server/src/todo.rs new file mode 100644 index 0000000..0664df3 --- /dev/null +++ b/rust/hello-server/src/todo.rs @@ -0,0 +1,70 @@ +use store::{ Action }; +use store::Action::{ Todos }; +use todo::TodoAction::{ Add, Toggle, Remove }; + +// Same Todo as last time... +#[derive(Clone, Debug, RustcEncodable, RustcDecodable)] +pub struct Todo { + pub id: i16, + pub title: String, + pub completed: bool, + pub deleted: bool, +} +impl Todo { + pub fn new(id: i16, title: String) -> Todo { + Todo { + id: id, + title: title, + completed: false, + deleted: false, + } + } +} + +// mark_done from the previous example becomes Toggle to align with the Redux example +// otherwise functionality is the same +#[derive(Clone, Debug)] +pub enum TodoAction { + Add(String), + Toggle(i16), + Remove(i16), +} + +// Helper function for getting a mutable todo from a vector by todo_id +pub fn get_mut_todo(todos: &mut Vec, todo_id: i16) -> Option<&mut Todo> { + todos.iter_mut().find(|todo| todo.id == todo_id) +} + +// Our todo reducer, takes in state (todo list) and returns a new/cloned version +// after applying the action (is applicable) +pub fn todo_reducer(state: &Vec, action: &Action) -> Vec { + let mut new_state: Vec = state.clone(); + + // First we make sure it's a `Todos` action, otherwise return clone of incoming state + match *action { + Todos(ref todo_action) => match *todo_action { + // Pretty simple from here on, check the type of Todos enum type + // If Add push a new item, and if `Toggle` or `Remove` user our get_mut_todo + // helper function and then change a property on the todo + Add(ref title) => { + let new_id = new_state.len() as i16 + 1; + new_state.push(Todo::new(new_id, title.to_string())) + }, + Toggle(todo_id) => { + if let Some(todo) = get_mut_todo(&mut new_state, todo_id) { + if todo.completed { todo.completed = false; } + else { todo.completed = true } + } + }, + Remove(todo_id) => { + if let Some(todo) = get_mut_todo(&mut new_state, todo_id) { + todo.deleted = true; + } + }, + }, + // If it's not a Todos action change nothing + _ => (), + } + + return new_state; +} \ No newline at end of file diff --git a/rust/hello-server/src/todos.tpl b/rust/hello-server/src/todos.tpl new file mode 100644 index 0000000..556003e --- /dev/null +++ b/rust/hello-server/src/todos.tpl @@ -0,0 +1,65 @@ + + + + + Nickel Todo + + + + + + +
+ +
+

Todos

+
+ +
+
+ +
+
    + {{#each todos}} + {{#unless deleted}} + {{#filter_todo}} + +
    + + + +
    + + {{/filter_todo}} + {{/unless}} + {{/each}} +
+
+ + + +
+ + \ No newline at end of file diff --git a/rust/intro/.gitignore b/rust/intro/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/rust/intro/.gitignore @@ -0,0 +1 @@ +target diff --git a/rust/intro/Cargo.toml b/rust/intro/Cargo.toml new file mode 100644 index 0000000..07db84c --- /dev/null +++ b/rust/intro/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "intro" +version = "0.1.0" +authors = ["Tim"] + +[dependencies] diff --git a/rust/intro/src/main.rs b/rust/intro/src/main.rs new file mode 100644 index 0000000..563ed12 --- /dev/null +++ b/rust/intro/src/main.rs @@ -0,0 +1,153 @@ +// Hey look, we are telling Rust we need to use HashMap from it's standard library +use std::collections::HashMap; + +fn number_types() { + // Let's print the min and max value for each type so you get a feel of when to use them + println!("i8 MIN {}", std::i8::MIN); + println!("i8 MAX {}", std::i8::MAX); + println!("i16 MIN {}", std::i16::MIN); + println!("i16 MAX {}", std::i16::MAX); + println!("i32 MIN {}", std::i32::MIN); + println!("i32 MAX {}", std::i32::MAX); + println!("i64 MIN {}", std::i64::MIN); + println!("i64 MAX {}", std::i64::MAX); + println!("u8 MIN {}", std::u8::MIN); + println!("u8 MAX {}", std::u8::MAX); + println!("u16 MIN {}", std::u16::MIN); + println!("u16 MAX {}", std::u16::MAX); + println!("u32 MIN {}", std::u32::MIN); + println!("u32 MAX {}", std::u32::MAX); + println!("u64 MIN {}", std::u64::MIN); + println!("u64 MAX {}", std::u64::MAX); + println!("f32 MIN {}", std::f32::MIN); + println!("f32 MAX {}", std::f32::MAX); + println!("f64 MIN {}", std::f64::MIN); + println!("f64 MAX {}", std::f64::MAX); +} + +fn structs_and_hashmaps() { + // Structs are great for representing data structures + struct Person { + name: String, + age: i16, + } + + // Using structs is basically like when defining them, but with values + let fredrik = Person { + name: "Fredrik".to_string(), + age: 33, + }; + + // Snake case is so much the convention that the compiler will ward you if you try to use camelCase + let unknown_person = Person { + name: "Unknown".to_string(), + age: 0, + }; + + println!("Hi there {} and {}", fredrik.name, unknown_person.name); + + // Let's create a HashMap, these work more or less as es6 Sets + // So when you want to hold arbitrary keys with arbitrary values HashMap is your new best friend + let mut ages = HashMap::new(); + + // Insert name as key and age as value into the HashMap + ages.insert(&fredrik.name, &fredrik.age); + ages.insert(&unknown_person.name, &unknown_person.age); + + // Print ages to see what we have, notice the {:?} instead of {} here? + // Complex types need to specify how they should be printed to work with {} + // {:?} instead makes use of a Debug trait in Rust, that can print + // almost anything, though not always in a very pretty, readable format + println!("ages {:?}", ages); + + // We can also remove stuff + ages.remove(&unknown_person.name); + println!("ages {:?}", ages); + + // And we can also get stuff of course + if let Some(fredrik_from_the_ages) = ages.get(&fredrik.name) { + println!("Fredrik's age is {}", fredrik_from_the_ages); + } +} + +fn vectors() { + // So we first specify the type of values the vector will hold, + // and then we call new to create an empty vector + // Also notice the `mut`, without it we can't push or change anything; + let mut fruits: Vec = Vec::new(); + + // Now we can push stuff to it + fruits.push("Banana".to_string()); + fruits.push("Banana".to_string()); + fruits.push("Banana".to_string()); + fruits.push("Orange".to_string()); + fruits.push("Orange".to_string()); + + // values can be access by index of course + println!("{} is a fruit", &fruits[0]); + + // for in should feel familiar + for fruit in &fruits { + println!("{}", fruit); + } + + // You can also loop over a range of integers like this + // This will let us print out the lines of that bad joke you probably saw coming + // When the fruits vector was populated ;) + for i in 0..fruits.len() { + // Match is the switch of Rust, it's smarter as you'll learn later, + // but this might as well be a switch + match i { + // {} creates a block so we can do more than one thing here + // => will expect one expression + 0 => { + println!("Knock, knock"); + println!("Who's there?"); + }, + 1 => { + println!("{}. Knock, knock", fruits[i]); + println!("Who's there?"); + }, + 2 => { + println!("{}. Knock, knock", fruits[i]); + println!("WHO'S THERE???"); + }, + 3 => { + println!("{}", fruits[i]); + println!("{}, who?", fruits[i]); + }, + 4 => { + println!("{} you glad I didn't say {}?", fruits[i], fruits[0]); + println!("facepalm"); + }, + // Rust wants to make sure your match statement always gets a match to avoid + // unexpected behaviors, `_` is the "default" or "catch all" rule + _ => println!("You are not even a fruit"), + } + } +} + +fn functional_vector_methods() { + // The `vec!` macro is a shorthand for creating vectors + let nums = vec![1,2,3,4,5]; + + // We need to specify the type here to make the compiler happy + let multiplied: &Vec = &nums + // Get an iterator from numbs + .iter() + // Map over it and multiply each number by 2 + .map(|num| num * 2) + // Filter out numbers that got too big for our taste + .filter(|num| *num < 8) + // collect the results into a new vector + .collect(); + + println!("Multiplied: {:?}", multiplied); +} + +fn main() { + number_types(); + structs_and_hashmaps(); + vectors(); + functional_vector_methods(); +} diff --git a/rust/todo-list-borrow/.gitignore b/rust/todo-list-borrow/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/rust/todo-list-borrow/.gitignore @@ -0,0 +1 @@ +target diff --git a/rust/todo-list-borrow/Cargo.toml b/rust/todo-list-borrow/Cargo.toml new file mode 100644 index 0000000..1f5f0d1 --- /dev/null +++ b/rust/todo-list-borrow/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "todo-list-borrow" +version = "0.1.0" +authors = ["Tim"] + +[dependencies] diff --git a/rust/todo-list-borrow/src/main.rs b/rust/todo-list-borrow/src/main.rs new file mode 100644 index 0000000..b75b4c8 --- /dev/null +++ b/rust/todo-list-borrow/src/main.rs @@ -0,0 +1,126 @@ +fn copyable() { + // All numeric primitives, signed and unsigned are copyable + let a: i64 = 10; + let b = a; + let c: f32 = 0.1; + let d = c; + println!("a and c are not moved: {} {}", a, c); + + // Bools too + let e = true; + let f = e; + println!("Bools are copyable too: {}", e); + + // Char's and string slices are copyable too, BUT NOT Strings + let g: char = 'g'; + let h = g; + let i = "string slices are copyable"; + let j = i; + println!("chars are copyable: {} aaaand {}", g, i); + + // Arrays and array slices too + let k = [1, 2, 3]; + let l = k; + // Get a slice of the first entry in the k array + let m = &k[0..1]; + let n = m; + println!("Arrays and array slices are copyable: {:?} {:?}", k, m); + + // Tuples too + let o = (1, "str"); + let p = o; + println!("Tuples are copyable {:?}", o); + + // That was the types that are copyable by default + // We can also implement Copy on our own types as long + // as they hold copyable types themselves + struct Point { + x: i32, + y: i32, + } + impl Copy for Point {} + impl Clone for Point { fn clone(&self) -> Point { *self } } + let q = Point { x: 1, y: 10 }; + let r = q; + println!("We made a copyable struct! {}/{}", q.x, q.y); + + // Phew! that was some black magic. Copy and Clone are `traits` that are built into the language + // Traits are basically interfaces that you can implement on your types + // You can create them yourselves too, which we'll get to in a future post + // There is an easier way to make a type copyable though: + #[derive(Copy, Clone, Debug)] + struct Point3D { + x: i32, + y: i32, + z: i32, + } + let s = Point3D { x: 1, y: -20, z: 30 }; + let t = s; + println!("Point3D is now copyable, and the Debug trait let's us print it easily {:?}", s); +} + +fn borrowing() { + + #[derive(Clone, Debug)] + struct Point { + x: i32, + y: i32, + } + + let a = Point { + x: 0, + y: 0, + }; + + // As discussed this would cause a `moved value` error + // let b = a; + // let c = a; + + // Borrowing is fine an unlimited amount of times though + let b = &a; + let c = &a; + + // But only for as long as the reference lives + // Rust is block scoped, basically any time you see {} it's a block + { + let d = Point { x: 0, y: 0 }; + + // This is totally fine + let e = &d; + + // But here, at the end of this block, d goes out of scope + } + + // So this would fail + // let f = &d; + + // You can also borrow a value mutably, but then the borrower has + // exclusive rights to mutate the value until it goes out of scope + let mut f = Point { x: 0, y: 0 }; + let g = &mut f; + + // This would fail + // let h = &mut f; + + // Even the owner can't mutate itself without causing an error + // f.x = 1; + + // This means that mutable borrows are mostly practical when passing something to a function + + fn pointifier(point: &mut Point, multiply_by: i32) { + point.x *= multiply_by; + point.y *= multiply_by; + } + + let mut h = Point { x: 1, y: 2 }; + pointifier(&mut h, 20); + + // once the pointifier function has finished, the borrow ends, and h is mutable again + h.x -= 1; + println!("Point {:?}", h); +} + +fn main() { + copyable(); + borrowing(); +} \ No newline at end of file diff --git a/rust/todo-list-redux/.gitignore b/rust/todo-list-redux/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/rust/todo-list-redux/.gitignore @@ -0,0 +1 @@ +target diff --git a/rust/todo-list-redux/Cargo.toml b/rust/todo-list-redux/Cargo.toml new file mode 100644 index 0000000..2083cd1 --- /dev/null +++ b/rust/todo-list-redux/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "todo-list-redux" +version = "0.1.0" +authors = ["Tim"] + +[dependencies] diff --git a/rust/todo-list-redux/src/main.rs b/rust/todo-list-redux/src/main.rs new file mode 100644 index 0000000..1fb265a --- /dev/null +++ b/rust/todo-list-redux/src/main.rs @@ -0,0 +1,250 @@ +use std::io; + +// Lets us type `Add("Todo item".to_string())` instead of `TodoAction::Add("Todo item".to_string()) +use TodoAction::{ Add, Remove, Toggle }; + +// Same with the Action enum and VisibilityFilter, Action::* would work too, but this way we list what we use +use Action::{ Todos, Visibility }; +use VisibilityFilter::{ ShowActive, ShowAll, ShowCompleted }; + +// Ripping off the canonical Redux todo example we'll add a +// visibility filter to our state in addation to the todos we already had +// This state struct will be the single source of state for our todo list program +#[derive(Clone, Debug)] +struct State { + todos: Vec, + visibility_filter: VisibilityFilter +} + +// By implementing a struct we are creating something very much like +// a class, we can attach methods to it refering to `&self` or `&mut self` +impl State { + // Can be called with State::default() + fn default() -> State { + State { + todos: Vec::new(), + visibility_filter: VisibilityFilter::ShowAll, + } + } +} + +// Same Todo as last time... +#[derive(Clone, Debug)] +struct Todo { + id: i16, + title: String, + completed: bool, + deleted: bool, +} + +// Create a convenient Todo::new(id, title) method +impl Todo { + pub fn new(id: i16, title: String) -> Todo { + Todo { + id: id, + title: title, + completed: false, + deleted: false, + } + } +} + +// Redux store implementation +struct Store { + state: State, + listeners: Vec, + reducer: fn(&State, Action) -> State, +} + +impl Store { + // Takes a reducer function as the only argument + // To keep it simple, State::default() privides the initial state in this example + fn create_store(reducer: fn(&State, Action) -> State) -> Store { + Store { + state: State::default(), + listeners: Vec::new(), + reducer: reducer, + } + } + + // Pushes a listener that will be called for any state change + fn subscribe(&mut self, listener: fn(&State)) { + self.listeners.push(listener); + } + + // Simply returns a borrowed reference to the state + #[allow(dead_code)] + fn get_state(&self) -> &State { + &self.state + } + + // Called for every new action, calls the reducer to update the state + // and then calls every listener with the new State + fn dispatch(&mut self, action: Action) { + self.state = (self.reducer)(&self.state, action); + for listener in &self.listeners { + listener(&self.state); + } + } +} + +// Rust has enums, so the enum type can replace the "type" property of Redux objects +// The enums will replace `action_creators` too since Todos(Add("Todo item".to_string())) +// is pretty clear +#[derive(Clone, Debug)] +enum Action { + Todos(TodoAction), + Visibility(VisibilityFilter), +} + +// mark_done from the previous example becomes Toggle to align with the Redux example +// otherwise functionality is the same +#[derive(Clone, Debug)] +enum TodoAction { + Add(String), + Toggle(i16), + Remove(i16), +} + +// Our 3 visibility states +#[derive(Clone, Debug)] +enum VisibilityFilter { + ShowActive, + ShowAll, + ShowCompleted, +} + +// Helper function for getting a mutable todo from a vector by todo_id +fn get_mut_todo(todos: &mut Vec, todo_id: i16) -> Option<&mut Todo> { + todos.iter_mut().find(|todo| todo.id == todo_id) +} + +// Our main reducer, returns a new State with the results of the child-reducers +// No combineReducers is implemented here, so it calls the child reducers +// by function name +fn reducer(state: &State, action: Action) -> State { + // Always return a new state + State { + todos: todo_reducer(&state.todos, &action), + visibility_filter: visibility_reducer(&state.visibility_filter, &action), + } +} + +// Our todo reducer, takes in state (todo list) and returns a new/cloned version +// after applying the action (is applicable) +fn todo_reducer(state: &Vec, action: &Action) -> Vec { + let mut new_state: Vec = state.clone(); + + // First we make sure it's a `Todos` action, otherwise return clone of incoming state + match *action { + Todos(ref todo_action) => match *todo_action { + // Pretty simple from here on, check the type of Todos enum type + // If Add push a new item, and if `Toggle` or `Remove` user our get_mut_todo + // helper function and then change a property on the todo + Add(ref title) => { + let new_id = new_state.len() as i16 + 1; + new_state.push(Todo::new(new_id, title.to_string())) + }, + Toggle(todo_id) => { + if let Some(todo) = get_mut_todo(&mut new_state, todo_id) { + if todo.completed { todo.completed = false; } + else { todo.completed = true } + } + }, + Remove(todo_id) => { + if let Some(todo) = get_mut_todo(&mut new_state, todo_id) { + todo.deleted = true; + } + }, + }, + // If it's not a Todos action change nothing + _ => (), + } + + return new_state; +} + +// Very simple reducer since the action will either be a VisibilityFilter, in which +// case we will return that, otherwise just return the incoming state +fn visibility_reducer(state: &VisibilityFilter, action: &Action) -> VisibilityFilter { + match *action { + Visibility(ref vis_action) => vis_action.clone(), + _ => state.clone(), + } +} + +// Very simple function to print a todo +fn print_todo(todo: &Todo) { + let done = if todo.completed { "✔" } else { " " }; + println!("[{}] {} {}", done, todo.id, todo.title); +} + +fn print_todos(state: &State) { + let visibility = &state.visibility_filter; + println!("\n\nTodo List:\n--------------------"); + for todo in &state.todos { + if !todo.deleted { + match *visibility { + ShowAll => print_todo(&todo), + ShowCompleted => if todo.completed { print_todo(&todo) }, + ShowActive => if !todo.completed { print_todo(&todo) }, + } + } + } + println!("-------------------\nVisibility filter: {:?}", visibility); + print_instructions(); +} + +fn print_instructions() { + println!("\nAvailable commands: \nadd [text] - toggle [id] - remove [id]\n show [all|active|completed]"); +} + +fn invalid_command(command: &str) { + println!("Invalid command: '{}'", command); +} + +fn main() { + // Let's create our store and subscribe with print_todos so every update is printed + let mut store = Store::create_store(reducer); + store.subscribe(print_todos); + + print_instructions(); + + // Same input handling as the last time, the interesting parts will be in our match statement + loop { + // Assign input lines to the `command` variable + let mut command = String::new(); + io::stdin() + .read_line(&mut command) + .expect("failed to read line"); + + // Split input on whitespace + let command_parts: Vec<&str> = command.split_whitespace().collect(); + + // Now match the size of the vector holding the separate words in the command + match command_parts.len() { + // If 0 we can't really do much + 0 => invalid_command(&command), + _ => { + match command_parts[0] { + // Since we prepared so well we just need to call dispatch on our store + // with the right action + "add" => store.dispatch(Todos(Add(command_parts[1..].join(" ").to_string() ))), + "remove" => if let Ok(num) = command_parts[1].parse::() { + store.dispatch(Todos(Remove(num))); + }, + "toggle" => if let Ok(num) = command_parts[1].parse::() { + store.dispatch(Todos(Toggle(num))); + }, + "show" => match command_parts[1] { + "all" => store.dispatch(Visibility(ShowAll)), + "active" => store.dispatch(Visibility(ShowActive)), + "completed" => store.dispatch(Visibility(ShowCompleted)), + _ => invalid_command(&command), + }, + _ => invalid_command(&command), + } + } + } + } +} \ No newline at end of file diff --git a/rust/todo-list/.gitignore b/rust/todo-list/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/rust/todo-list/.gitignore @@ -0,0 +1 @@ +target diff --git a/rust/todo-list/Cargo.toml b/rust/todo-list/Cargo.toml new file mode 100644 index 0000000..d1744d3 --- /dev/null +++ b/rust/todo-list/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "todo-list" +version = "0.1.0" +authors = ["Tim"] + +[dependencies] diff --git a/rust/todo-list/src/main.rs b/rust/todo-list/src/main.rs new file mode 100644 index 0000000..ca1234e --- /dev/null +++ b/rust/todo-list/src/main.rs @@ -0,0 +1,99 @@ +use std::io; + +struct Todo { + id: i16, + title: String, + completed: bool, + deleted: bool, +} + +fn add_todo(todos: &mut Vec, title: &str) { + // The size of the vector + 1 makes a decent enough id + let new_id = todos.len() as i16 + 1; + todos.push(Todo { + id: new_id, + title: title.to_string(), + completed: false, + deleted: false, + }); +} + +fn remove_todo(todos: &mut Vec, todo_id: i16) { + if let Some(todo) = todos.iter_mut().find(|todo| todo.id == todo_id) { + todo.deleted = true; + } +} + +fn mark_done(todos: &mut Vec, todo_id: i16) { + if let Some(todo) = todos.iter_mut().find(|todo| todo.id == todo_id) { + todo.completed = true; + } +} + +fn print_todos(todos: &Vec) { + println!("\n\nTodo List:\n--------------------"); + for todo in todos { + if !todo.deleted { + let done = if todo.completed { "✔" } else { " " }; + println!("[{}] {} {}", done, todo.id, todo.title); + } + } +} + +fn invalid_command(command: &str) { + println!("Invalid command: '{}'", command); +} + +fn main() { + let mut todos: Vec = Vec::new(); + // Print the todo list on start up + print_todos(&todos); + + // Infinite loop + loop { + // Assign input lines to the `command` variable + let mut command = String::new(); + io::stdin() + .read_line(&mut command) + .expect("failed to read line"); + + // Split input on whitespace + let command_parts: Vec<&str> = command.split_whitespace().collect(); + + // Now match the size of the vector holding the separate words in the command + match command_parts.len() { + // If 0 we can't really do much + 0 => invalid_command(&command), + // If the length is 1 it's a `list` command, or an invalid command + 1 => match command_parts[0] { + "list" => print_todos(&todos), + "quit" => break, + "exit" => break, + _ => invalid_command(&command), + }, + // If the length is bigger than 1 we look for an `add x x x`, `remove x x x`, or `done x` + _ => { + // Match the first word in the command + match command_parts[0] { + // If add, let's join up all words except the first one as our todo title + // `[1..]` means from index 1 in the vector to the end + "add" => add_todo(&mut todos, &command_parts[1..].join(" ")), + // For remove and done we want to send in a todo_id, + // so we parse the string as an integer + // parse returns a `Result` that is either `Ok` or `Err`, + // so we can handle it much as an `Option` return + "remove" => if let Ok(num) = command_parts[1].parse::() { + remove_todo(&mut todos, num) + }, + "done" => if let Ok(num) = command_parts[1].parse::() { + mark_done(&mut todos, num) + }, + _ => invalid_command(&command), + } + }, + } + + // At the end of each loop print the list + print_todos(&todos); + } +} \ No newline at end of file