Add rust tutorials
This commit is contained in:
parent
1ac21a44f1
commit
aae3029cd7
|
@ -0,0 +1 @@
|
|||
target
|
|
@ -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"
|
|
@ -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::<i16>() {
|
||||
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");
|
||||
}
|
|
@ -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<Todo>,
|
||||
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<fn(&State)>,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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>, 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<Todo>, action: &Action) -> Vec<Todo> {
|
||||
let mut new_state: Vec<Todo> = 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;
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Nickel Todo</title>
|
||||
<link rel="stylesheet" href="http://todomvc.com/examples/react/node_modules/todomvc-common/base.css" />
|
||||
<link rel="stylesheet" href="http://todomvc.com/examples/react/node_modules/todomvc-app-css/index.css" />
|
||||
<style>
|
||||
label a {
|
||||
color: #4d4d4d;
|
||||
test-decoration: none;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
document.addEventListener('click', function clickHandler(e) {
|
||||
if (e.target && e.target.dataset && e.target.dataset.id) {
|
||||
window.location.href = '/' + e.target.dataset.action + '/' + e.target.dataset.id;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<section class="todoapp">
|
||||
|
||||
<header class="header">
|
||||
<h1>Todos</h1>
|
||||
<form class="add-todo" action="/" method="post">
|
||||
<input class="new-todo" placeholder="What needs to be done?" name="todo" />
|
||||
</form>
|
||||
</header>
|
||||
|
||||
<main class="main">
|
||||
<ul class="todo-list">
|
||||
{{#each todos}}
|
||||
{{#unless deleted}}
|
||||
{{#filter_todo}}
|
||||
<li{{#if completed}} class="completed"{{/if}} data-id={{id}}>
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox"{{#if completed}}checked="checked"{{/if}} data-id={{id}} data-action="toggle" />
|
||||
<label><a href="/toggle/{{id}}">{{title}}</a></label>
|
||||
<button class="destroy" data-id="{{id}}" data-action="remove"></button>
|
||||
</div>
|
||||
</li>
|
||||
{{/filter_todo}}
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<span class="todo-count">
|
||||
<strong>{{#active_count}}{{/active_count}}</strong>
|
||||
</span>
|
||||
<ul class="filters">
|
||||
<li><a href="/show/all"{{#is_selected_filter "ShowAll"}} class="selected"{{/is_selected_filter}}>All</a></li>
|
||||
<span> </span>
|
||||
<li><a href="/show/active"{{#is_selected_filter "ShowActive"}} class="selected"{{/is_selected_filter}}>Active</a></li>
|
||||
<span> </span>
|
||||
<li><a href="/show/completed"{{#is_selected_filter "ShowCompleted"}} class="selected"{{/is_selected_filter}}>Completed</a></li>
|
||||
</ul>
|
||||
</footer>
|
||||
|
||||
</section>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1 @@
|
|||
target
|
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "intro"
|
||||
version = "0.1.0"
|
||||
authors = ["Tim"]
|
||||
|
||||
[dependencies]
|
|
@ -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<String> = 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<i32> = &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();
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
target
|
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "todo-list-borrow"
|
||||
version = "0.1.0"
|
||||
authors = ["Tim"]
|
||||
|
||||
[dependencies]
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
target
|
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "todo-list-redux"
|
||||
version = "0.1.0"
|
||||
authors = ["Tim"]
|
||||
|
||||
[dependencies]
|
|
@ -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<Todo>,
|
||||
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<fn(&State)>,
|
||||
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>, 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<Todo>, action: &Action) -> Vec<Todo> {
|
||||
let mut new_state: Vec<Todo> = 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::<i16>() {
|
||||
store.dispatch(Todos(Remove(num)));
|
||||
},
|
||||
"toggle" => if let Ok(num) = command_parts[1].parse::<i16>() {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
target
|
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "todo-list"
|
||||
version = "0.1.0"
|
||||
authors = ["Tim"]
|
||||
|
||||
[dependencies]
|
|
@ -0,0 +1,99 @@
|
|||
use std::io;
|
||||
|
||||
struct Todo {
|
||||
id: i16,
|
||||
title: String,
|
||||
completed: bool,
|
||||
deleted: bool,
|
||||
}
|
||||
|
||||
fn add_todo(todos: &mut Vec<Todo>, 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>, 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>, todo_id: i16) {
|
||||
if let Some(todo) = todos.iter_mut().find(|todo| todo.id == todo_id) {
|
||||
todo.completed = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn print_todos(todos: &Vec<Todo>) {
|
||||
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<Todo> = 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::<i16>() {
|
||||
remove_todo(&mut todos, num)
|
||||
},
|
||||
"done" => if let Ok(num) = command_parts[1].parse::<i16>() {
|
||||
mark_done(&mut todos, num)
|
||||
},
|
||||
_ => invalid_command(&command),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// At the end of each loop print the list
|
||||
print_todos(&todos);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue