Document not found (404)
+This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ + +diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..e9c072897 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +book \ No newline at end of file diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..f17311098 --- /dev/null +++ b/.nojekyll @@ -0,0 +1 @@ +This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/404.html b/404.html new file mode 100644 index 000000000..b1e2e045c --- /dev/null +++ b/404.html @@ -0,0 +1,203 @@ + + +
+ + +This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ + +++// Make it work +use std::mem::size_of_val; +fn main() { + let c1 = 'a'; + assert_eq!(size_of_val(&c1),1); + + let c2 = '中'; + assert_eq!(size_of_val(&c2),3); + + println!("Success!"); +}
++// Make it work +fn main() { + let c1 = "中"; + print_char(c1); +} + +fn print_char(c : char) { + println!("{}", c); +}
++// Make println! work +fn main() { + let _f: bool = false; + + let t = true; + if !t { + println!("Success!"); + } +}
++// Make it work +fn main() { + let f = true; + let t = true && false; + assert_eq!(t, f); + + println!("Success!"); +}
++// Make it work, don't modify `implicitly_ret_unit` ! +fn main() { + let _v: () = (); + + let v = (2, 3); + assert_eq!(v, implicitly_ret_unit()); + + println!("Success!"); +} + +fn implicitly_ret_unit() { + println!("I will return a ()"); +} + +// Don't use this one +fn explicitly_ret_unit() -> () { + println!("I will return a ()"); +}
++// Modify `4` in assert to make it work +use std::mem::size_of_val; +fn main() { + let unit: () = (); + assert!(size_of_val(&unit) == 4); + + println!("Success!"); +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it
+
++fn main() { + // Don't modify the following two lines! + let (x, y) = (1, 2); + let s = sum(x, y); + + assert_eq!(s, 3); + + println!("Success!"); +} + +fn sum(x, y: i32) { + x + y; +}
+fn main() { + print(); +} + +// Replace i32 with another type +fn print() -> i32 { + println!("Success!"); +}
+// Solve it in two ways +// DON'T let `println!` work +fn main() { + never_return(); + + println!("Failed!"); +} + +fn never_return() -> ! { + // Implement this function, don't modify the fn signatures + +}
Diverging functions never return to the caller, so they may be used in places where a value of any type is expected.
+++fn main() { + println!("Success!"); +} + +fn get_option(tp: u8) -> Option<i32> { + match tp { + 1 => { + // TODO + } + _ => { + // TODO + } + }; + + // Rather than returning a None, we use a diverging function instead + never_return_fn() +} + +// IMPLEMENT this function in THREE ways +fn never_return_fn() -> ! { + +}
++fn main() { + // FILL in the blank + let b = __; + + let _v = match b { + true => 1, + // Diverging functions can also be used in match expression to replace a value of any value + false => { + println!("Success!"); + panic!("we have no value for `false`, but we can panic"); + } + }; + + println!("Exercise Failed if printing out this line!"); +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it
+
Learning resources:
+++Tips: If we don't explicitly assign a type to a variable, then the compiler will infer one for us.
+
++// Remove something to make it work +fn main() { + let x: i32 = 5; + let mut y: u32 = 5; + + y = x; + + let z = 10; // Type of z ? + + println!("Success!"); +}
++// Fill the blank +fn main() { + let v: u16 = 38_u8 as __; + + println!("Success!"); +}
++Tips: If we don't explicitly assign a type to a variable, then the compiler will infer one for us.
+
++// Modify `assert_eq!` to make it work +fn main() { + let x = 5; + assert_eq!("u32".to_string(), type_of(&x)); + + println!("Success!"); +} + +// Get the type of given variable, return a string representation of the type , e.g "i8", "u8", "i32", "u32" +fn type_of<T>(_: &T) -> String { + format!("{}", std::any::type_name::<T>()) +}
++// Fill the blanks to make it work +fn main() { + assert_eq!(i8::MAX, __); + assert_eq!(u8::MAX, __); + + println!("Success!"); +}
++// Fix errors and panics to make it work +fn main() { + let v1 = 251_u8 + 8; + let v2 = i8::checked_add(251, 8).unwrap(); + println!("{},{}",v1,v2); +}
++// Modify `assert!` to make it work +fn main() { + let v = 1_024 + 0xff + 0o77 + 0b1111_1111; + assert!(v == 1579); + + println!("Success!"); +}
++// Fill the blank to make it work +fn main() { + let x = 1_000.000_1; // ? + let y: f32 = 0.12; // f32 + let z = 0.01_f64; // f64 + + assert_eq!(type_of(&x), "__".to_string()); + println!("Success!"); +} + +fn type_of<T>(_: &T) -> String { + format!("{}", std::any::type_name::<T>()) +}
++fn main() { + assert!(0.1+0.2==0.3); + + println!("Success!"); +}
assert!
to make it work 2. Make println!
output: 97 - 122+fn main() { + let mut sum = 0; + for i in -3..2 { + sum += i + } + + assert!(sum == -3); + + for c in 'a'..='z' { + println!("{}",c); + } +}
++// Fill the blanks +use std::ops::{Range, RangeInclusive}; +fn main() { + assert_eq!((1..__), Range{ start: 1, end: 5 }); + assert_eq!((1..__), RangeInclusive::new(1, 5)); + + println!("Success!"); +}
++// Fill the blanks and fix the errors +fn main() { + // Integer addition + assert!(1u32 + 2 == __); + + // Integer subtraction + assert!(1i32 - 2 == __); + assert!(1u8 - 2 == -1); + + assert!(3 * 50 == __); + + assert!(9.6 / 3.2 == 3.0); // error ! make it work + + assert!(24 % 5 == __); + // Short-circuiting boolean logic + assert!(true && false == __); + assert!(true || false == __); + assert!(!true == __); + + // Bitwise operations + println!("0011 AND 0101 is {:04b}", 0b0011u32 & 0b0101); + println!("0011 OR 0101 is {:04b}", 0b0011u32 | 0b0101); + println!("0011 XOR 0101 is {:04b}", 0b0011u32 ^ 0b0101); + println!("1 << 5 is {}", 1u32 << 5); + println!("0x80 >> 2 is 0x{:x}", 0x80u32 >> 2); +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it
+
+fn main() { + let x = 5u32; + + let y = { + let x_squared = x * x; + let x_cube = x_squared * x; + + // This expression will be assigned to `y` + x_cube + x_squared + x + }; + + let z = { + // The semicolon suppresses this expression and `()` is assigned to `z` + 2 * x; + }; + + println!("x is {:?}", x); + println!("y is {:?}", y); + println!("z is {:?}", z); +}
+// Make it work with two ways +fn main() { + let v = { + let mut x = 1; + x += 2 + }; + + assert_eq!(v, 3); + + println!("Success!"); +}
++fn main() { + let v = (let x = 3); + + assert!(v == 3); + + println!("Success!"); +}
++fn main() { + let s = sum(1 , 2); + assert_eq!(s, 3); + + println!("Success!"); +} + +fn sum(x: i32, y: i32) -> i32 { + x + y; +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it
+
Where vectors store values by an integer index, HashMaps store values by key. It is a hash map implemented with quadratic probing and SIMD lookup. By default, HashMap
uses a hashing algorithm selected to provide resistance against HashDoS attacks.
The default hashing algorithm is currently SipHash 1-3
, though this is subject to change at any point in the future. While its performance is very competitive for medium sized keys, other hashing algorithms will outperform it for small keys such as integers as well as large keys such as long strings, though those algorithms will typically not protect against attacks such as HashDoS.
The hash table implementation is a Rust port of Google’s SwissTable. The original C++ version of SwissTable can be found here, and this CppCon talk gives an overview of how the algorithm works.
+++// FILL in the blanks and FIX the errors +use std::collections::HashMap; +fn main() { + let mut scores = HashMap::new(); + scores.insert("Sunface", 98); + scores.insert("Daniel", 95); + scores.insert("Ashley", 69.0); + scores.insert("Katie", "58"); + + // Get returns an Option<&V> + let score = scores.get("Sunface"); + assert_eq!(score, Some(98)); + + if scores.contains_key("Daniel") { + // Indexing returns a value V + let score = scores["Daniel"]; + assert_eq!(score, __); + scores.remove("Daniel"); + } + + assert_eq!(scores.len(), __); + + for (name, score) in scores { + println!("The score of {} is {}", name, score); + } +}
++use std::collections::HashMap; +fn main() { + let teams = [ + ("Chinese Team", 100), + ("American Team", 10), + ("France Team", 50), + ]; + + let mut teams_map1 = HashMap::new(); + for team in &teams { + teams_map1.insert(team.0, team.1); + } + + // IMPLEMENT team_map2 in two ways + // Tips: one of the approaches is to use `collect` method + let teams_map2... + + assert_eq!(teams_map1, teams_map2); + + println!("Success!"); +}
++// FILL in the blanks +use std::collections::HashMap; +fn main() { + // Type inference lets us omit an explicit type signature (which + // would be `HashMap<&str, u8>` in this example). + let mut player_stats = HashMap::new(); + + // Insert a key only if it doesn't already exist + player_stats.entry("health").or_insert(100); + + assert_eq!(player_stats["health"], __); + + // Insert a key using a function that provides a new value only if it + // doesn't already exist + player_stats.entry("health").or_insert_with(random_stat_buff); + assert_eq!(player_stats["health"], __); + + // Ensures a value is in the entry by inserting the default if empty, and returns + // a mutable reference to the value in the entry. + let health = player_stats.entry("health").or_insert(50); + assert_eq!(health, __); + *health -= 50; + assert_eq!(*health, __); + + println!("Success!"); +} + +fn random_stat_buff() -> u8 { + // Could actually return some random value here - let's just return + // some fixed value for now + 42 +}
Any type that implements the Eq
and Hash
traits can be a key in HashMap
. This includes:
bool
(though not very useful since there is only two possible keys)int
, uint
, and all variations thereofString
and &str
(tips: you can have a HashMap
keyed by String
and call .get()
with an &str
)Note that f32
and f64
do not implement Hash
, likely because floating-point precision errors would make using them as hashmap keys horribly error-prone.
All collection classes implement Eq
and Hash
if their contained type also respectively implements Eq
and Hash
. For example, Vec<T>
will implement Hash
if T
implements Hash
.
++// FIX the errors +// Tips: `derive` is usually a good way to implement some common used traits +use std::collections::HashMap; + +struct Viking { + name: String, + country: String, +} + +impl Viking { + /// Creates a new Viking. + fn new(name: &str, country: &str) -> Viking { + Viking { + name: name.to_string(), + country: country.to_string(), + } + } +} + +fn main() { + // Use a HashMap to store the vikings' health points. + let vikings = HashMap::from([ + (Viking::new("Einar", "Norway"), 25), + (Viking::new("Olaf", "Denmark"), 24), + (Viking::new("Harald", "Iceland"), 12), + ]); + + // Use derived implementation to print the status of the vikings. + for (viking, health) in &vikings { + println!("{:?} has {} hp", viking, health); + } +}
Like vectors, HashMaps are growable, but HashMaps can also shrink themselves when they have excess space. You can create a HashMap
with a certain starting capacity using HashMap::with_capacity(uint)
, or use HashMap::new()
to get a HashMap with a default initial capacity (recommended).
++use std::collections::HashMap; +fn main() { + let mut map: HashMap<i32, i32> = HashMap::with_capacity(100); + map.insert(1, 2); + map.insert(3, 4); + // Indeed ,the capacity of HashMap is not 100, so we can't compare the equality here. + assert!(map.capacity() >= 100); + + // Shrinks the capacity of the map with a lower limit. It will drop + // down no lower than the supplied limit while maintaining the internal rules + // and possibly leaving some space in accordance with the resize policy. + + map.shrink_to(50); + assert!(map.capacity() >= 50); + + // Shrinks the capacity of the map as much as possible. It will drop + // down as much as possible while maintaining the internal rules + // and possibly leaving some space in accordance with the resize policy. + map.shrink_to_fit(); + assert!(map.capacity() >= 2); + println!("Success!"); +}
For types that implement the Copy
trait, like i32
, the values are copied into HashMap
. For owned values like String
, the values will be moved and HashMap
will be the owner of those values.
+// FIX the errors with least changes +// DON'T remove any code line +use std::collections::HashMap; +fn main() { + let v1 = 10; + let mut m1 = HashMap::new(); + m1.insert(v1, v1); + println!("v1 is still usable after inserting to hashmap : {}", v1); + + let v2 = "hello".to_string(); + let mut m2 = HashMap::new(); + // Ownership moved here + m2.insert(v2, v1); + + assert_eq!(v2, "hello"); + + println!("Success!"); +}
If the performance of SipHash 1-3
doesn't meet your requirements, you can find replacements in crates.io or github.com.
The usage of third-party hash looks like this:
++ + +#![allow(unused)] +fn main() { +use std::hash::BuildHasherDefault; +use std::collections::HashMap; +// Introduce a third party hash function +use twox_hash::XxHash64; + + +let mut hash: HashMap<_, _, BuildHasherDefault<XxHash64>> = Default::default(); +hash.insert(42, "the answer"); +assert_eq!(hash.get(&42), Some(&"the answer")); +}
Learning resources:
+std::string::String
is a UTF-8 encoded, growable string. It is the most common string type we used in daily development, it also has ownership over the string contents.
++// FILL in the blanks and FIX errors +// 1. Don't use `to_string()` +// 2. Don't add/remove any code line +fn main() { + let mut s: String = "hello, "; + s.push_str("world".to_string()); + s.push(__); + + move_ownership(s); + + assert_eq!(s, "hello, world!"); + + println!("Success!"); +} + +fn move_ownership(s: String) { + println!("ownership of \"{}\" is moved here!", s) +}
A String
is stored as a vector of bytes (Vec<u8>
), but guaranteed to always be a valid UTF-8 sequence. String
is heap allocated, growable and not null terminated.
&str
is a slice (&[u8]
) that always points to a valid UTF-8 sequence, and can be used to view into a String, just like &[T]
is a view into Vec<T>
.
+// FILL in the blanks +fn main() { + let mut s = String::from("hello, world"); + + let slice1: &str = __; // In two ways + assert_eq!(slice1, "hello, world"); + + let slice2 = __; + assert_eq!(slice2, "hello"); + + let slice3: __ = __; + slice3.push('!'); + assert_eq!(slice3, "hello, world!"); + + println!("Success!"); +}
++// Question: how many heap allocations are happening here? +// Your answer: +fn main() { + // Create a String type based on `&str` + // The type of string literals is `&str` + let s: String = String::from("hello, world!"); + + // Create a slice point to String `s` + let slice: &str = &s; + + // Create a String type based on the recently created slice + let s: String = slice.to_string(); + + assert_eq!(s, "hello, world!"); + + println!("Success!"); +}
Strings are always valid UTF-8. This has a few implications:
+Indexing is intended to be a constant-time operation, but UTF-8 encoding does not allow us to do this. Furthermore, it’s not clear what sort of thing the index should return: a byte, a codepoint, or a grapheme cluster. The bytes and chars methods return iterators over the first two, respectively.
+&s1[start..end]
.++// FILL in the blank and FIX errors +fn main() { + let s = String::from("hello, 世界"); + let slice1 = s[0]; //tips: `h` only takes 1 byte in UTF8 format + assert_eq!(slice1, "h"); + + let slice2 = &s[3..5]; // Tips: `中` takes 3 bytes in UTF8 format + assert_eq!(slice2, "世"); + + // Iterate through all chars in s + for (i, c) in s.__ { + if i == 7 { + assert_eq!(c, '世') + } + } + + println!("Success!"); +}
You can use utf8_slice to slice UTF8 string, it can index chars instead of bytes.
+Example
++use utf8_slice; +fn main() { + let s = "The 🚀 goes to the 🌑!"; + + let rocket = utf8_slice::slice(s, 4, 5); + // Will equal "🚀" +}
++Tips: maybe you need
+from_utf8
method
++// FILL in the blanks +fn main() { + let mut s = String::new(); + __; + + // Some bytes, in a vector + let v = vec![104, 101, 108, 108, 111]; + + // Turn a byte's vector into a String + let s1 = __; + + + assert_eq!(s, s1); + + println!("Success!"); +}
A String is made up of three components: a pointer to some bytes, a length, and a capacity.
+The pointer points to an internal buffer String uses to store its data. The length is the number of bytes currently stored in the buffer( always stored on the heap ), and the capacity is the size of the buffer in bytes. As such, the length will always be less than or equal to the capacity.
+++// Modify the code below to print out: +// 25 +// 25 +// 25 +// Here, there’s no need to allocate more memory inside the loop. +fn main() { + let mut s = String::new(); + + println!("{}", s.capacity()); + + for _ in 0..2 { + s.push_str("hello"); + println!("{}", s.capacity()); + } + + println!("Success!"); +}
++// FILL in the blanks +use std::mem; + +fn main() { + let story = String::from("Rust By Practice"); + + // Prevent automatically dropping of the String's data + let mut story = mem::ManuallyDrop::new(story); + + let ptr = story.__(); + let len = story.__(); + let capacity = story.__(); + + assert_eq!(16, len); + + // We can rebuild a String out of ptr, len, and capacity. This is all + // unsafe because we are responsible for making sure the components are + // valid: + let s = unsafe { String::from_raw_parts(ptr, len, capacity) }; + + assert_eq!(*story, s); + + println!("Success!"); +}
More exercises of String methods can be found here.
+++ + +You can find the solutions here(under the solutions path), but only use it when you need it
+
Vectors are resizable arrays. Like slices, their size is not known at compile time, but they can grow or shrink at any time.
+++fn main() { + let arr: [u8; 3] = [1, 2, 3]; + + let v = Vec::from(arr); + is_vec(&v); + + let v = vec![1, 2, 3]; + is_vec(&v); + + // vec!(..) and vec![..] are same macros, so + let v = vec!(1, 2, 3); + is_vec(&v); + + // In code below, v is Vec<[u8; 3]> , not Vec<u8> + // USE Vec::new and `for` to rewrite the below code + let v1 = vec!(arr); + is_vec(&v1); + + assert_eq!(v, v1); + + println!("Success!"); +} + +fn is_vec(v: &Vec<u8>) {}
extend
method++// FILL in the blank +fn main() { + let mut v1 = Vec::from([1, 2, 4]); + v1.pop(); + v1.push(3); + + let mut v2 = Vec::new(); + v2.__; + + assert_eq!(v1, v2); + + println!("Success!"); +}
++// FILL in the blanks +fn main() { + // Array -> Vec + // impl From<[T; N]> for Vec + let arr = [1, 2, 3]; + let v1 = __(arr); + let v2: Vec<i32> = arr.__(); + + assert_eq!(v1, v2); + + + // String -> Vec + // impl From<String> for Vec + let s = "hello".to_string(); + let v1: Vec<u8> = s.__(); + + let s = "hello".to_string(); + let v2 = s.into_bytes(); + assert_eq!(v1, v2); + + // impl<'_> From<&'_ str> for Vec + let s = "hello"; + let v3 = Vec::__(s); + assert_eq!(v2, v3); + + // Iterators can be collected into vectors + let v4: Vec<i32> = [0; 10].into_iter().collect(); + assert_eq!(v4, vec![0; 10]); + + println!("Success!"); + }
++// FIX the error and IMPLEMENT the code +fn main() { + let mut v = Vec::from([1, 2, 3]); + for i in 0..5 { + println!("{:?}", v[i]) + } + + for i in 0..5 { + // IMPLEMENT the code here... + } + + assert_eq!(v, vec![2, 3, 4, 5, 6]); + + println!("Success!"); +}
A Vec can be mutable. On the other hand, slices are read-only objects. To get a slice, use &
.
In Rust, it’s more common to pass slices as arguments rather than vectors when you just want to provide read access. The same goes for String
and &str
.
++// FIX the errors +fn main() { + let mut v = vec![1, 2, 3]; + + let slice1 = &v[..]; + // Out of bounds will cause a panic + // You must use `v.len` here + let slice2 = &v[0..4]; + + assert_eq!(slice1, slice2); + + // Slices are read only + // Note: slice and &Vec are different + let vec_ref: &mut Vec<i32> = &mut v; + (*vec_ref).push(4); + let slice3 = &mut v[0..3]; + slice3.push(4); + + assert_eq!(slice3, &[1, 2, 3, 4]); + + println!("Success!"); +}
The capacity of a vector is the amount of space allocated for any future elements that will be added onto the vector. This is not to be confused with the length of a vector, which specifies the number of actual elements within the vector. If a vector’s length exceeds its capacity, its capacity will automatically be increased, but its elements will have to be reallocated.
+For example, a vector with capacity 10 and length 0 would be an empty vector with space for 10 more elements. Pushing 10 or fewer elements onto the vector will not change its capacity or cause reallocation to occur. However, if the vector’s length is increased to 11, it will have to reallocate, which can be slow. For this reason, it is recommended to use Vec::with_capacity
whenever possible to specify how big the vector is expected to get.
+// FIX the errors +fn main() { + let mut vec = Vec::with_capacity(10); + + // The vector contains no items, even though it has capacity for more + assert_eq!(vec.len(), __); + assert_eq!(vec.capacity(), 10); + + // These are all done without reallocating... + for i in 0..10 { + vec.push(i); + } + assert_eq!(vec.len(), __); + assert_eq!(vec.capacity(), __); + + // ...but this may make the vector reallocate + vec.push(11); + assert_eq!(vec.len(), 11); + assert!(vec.capacity() >= 11); + + + // Fill in an appropriate value to make the `for` done without reallocating + let mut vec = Vec::with_capacity(__); + for i in 0..100 { + vec.push(i); + } + + assert_eq!(vec.len(), __); + assert_eq!(vec.capacity(), __); + + println!("Success!"); +}
The elements in a vector must be the same type, for example , the code below will cause an error:
++fn main() { + let v = vec![1, 2.0, 3]; +}
But we can use enums or trait objects to store distinct types.
++#[derive(Debug)] +enum IpAddr { + V4(String), + V6(String), +} +fn main() { + // FILL in the blank + let v : Vec<IpAddr>= __; + + // Comparing two enums need to derive the PartialEq trait + assert_eq!(v[0], IpAddr::V4("127.0.0.1".to_string())); + assert_eq!(v[1], IpAddr::V6("::1".to_string())); + + println!("Success!"); +}
+ + +trait IpAddr { + fn display(&self); +} + +struct V4(String); +impl IpAddr for V4 { + fn display(&self) { + println!("ipv4: {:?}",self.0) + } +} +struct V6(String); +impl IpAddr for V6 { + fn display(&self) { + println!("ipv6: {:?}",self.0) + } +} + +fn main() { + // FILL in the blank + let v: __= vec![ + Box::new(V4("127.0.0.1".to_string())), + Box::new(V6("::1".to_string())), + ]; + + for ip in v { + ip.display(); + } +}
Every program requires comments:
+// Line comment, which goes to the end of the line
/* Block comment, which goes to the end of the closing delimiter */
+fn main() { + // This is an example of a line comment + // There are two slashes at the beginning of the line + // And nothing written inside these will be read by the compiler + + // println!("Hello, world!"); + + // Run it. See? Now try deleting the two slashes, and run it again. + + /* + * This is another type of comment, a block comment. In general, + * line comments are the recommended comment style. But + * block comments are extremely useful for temporarily disabling + * chunks of code. /* Block comments can be /* nested, */ */ + * so it takes only a few keystrokes to comment out everything + * in this main() function. /*/*/* Try it yourself! */*/*/ + */ + + /* + Note: The previous column of `*` was entirely for style. There's + no actual need for it. + */ +}
++/* Make it work, only using comments! */ +fn main() { + todo!(); + unimplemented!(); + + assert_eq!(6, 5 + 3 + 2 + 1 ) +}
Markdown
+/// Generate library docs for the following item
//! Generate library docs for the eclosing item
Before starting, we need to create a new package for practice: cargo new --lib doc-comments
.
///
Add docs for function add_one
+#![allow(unused)] +fn main() { +// in lib.rs + +/// Add one to the given value and return the value +/// +/// # Examples +/// +/// ``` +/// let arg = 5; +/// let answer = my_crate::add_one(arg); +/// +/// assert_eq!(6, answer); +/// ``` +pub fn add_one(x: i32) -> i32 { + x + 1 +} +}
We can use cargo doc --open
to generate html files and open them in the browser.
/** ... */
Add docs for function add_two
:
+#![allow(unused)] +fn main() { +/** Add two to the given value and return a new value + +Examples + +let arg = 5; +let answer = my_crate::add_two(arg); + +assert_eq!(7, answer); + +*/ +pub fn add_two(x: i32) -> i32 { + x + 2 +} +}
We can also add doc comments for our crates and modules.
+Firstly, let's add some doc comments for our library crate:
+++Note: We must place crates and module comments at the top of crate root or module file.
+
+#![allow(unused)] +fn main() { +//! # Doc comments +//! +//! A library for showing how to use doc comments + +// in lib.rs +pub mod compute; +}
You can also use block comments to achieve this:
++#![allow(unused)] +fn main() { +/*! # Doc comments + + A library for showing how to use doc comments */ +}
Next, create a new module file src/compute.rs
, and add following comments to it:
+#![allow(unused)] +fn main() { +//! //! Do some complicated arithmetic that you can't do by yourself + +// in compute.rs +}
Then run cargo doc --open
and see the results.
The doc comments of add_one
and add_two
contain two example code blocks.
The examples can not only demonstrate how to use your library, but also running as test with cargo test
command.
cargo test
to get following result: running 0 tests
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
+
+ Doc-tests doc-comments
+
+running 2 tests
+test src/lib.rs - add_one (line 11) ... ok
+test src/lib.rs - add_two (line 26) ... ok
+
+test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.55s
+
+src/compute.rs
and make the cargo test
passed.++You can only modify the comments, DON'T modify
+fn div
+#![allow(unused)] +fn main() { +// in src/compute.rs + +/// # Panics +/// +/// The function panics if the second argument is zero. +/// +/// ```rust,should_panic +/// // panics on division by zero +/// doc_comments::compute::div(10, 0); +/// ``` +pub fn div(a: i32, b: i32) -> i32 { + if b == 0 { + panic!("Divide-by-zero error"); + } + + a / b +} +}
Add following code to src/compute.rs
,
+// in src/compute.rs + +/// ``` +/// # fn try_main() -> Result<(), String> { +/// # let res = doc_comments::compute::try_div(10, 0)?; +/// # Ok(()) // returning from try_main +/// # } +/// # fn main() { +/// # try_main().unwrap(); +/// # +/// # } +/// ``` +pub fn try_div(a: i32, b: i32) -> Result<i32, String> { + if b == 0 { + Err(String::from("Divide-by-zero")) + } else { + Ok(a / b) + } +}
and modify this code to achieve two goals:
+cargo doc --open
running 0 tests
+
+test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
+
+ Doc-tests doc-comments
+
+running 4 tests
+test src/compute.rs - compute::div (line 7) ... ok
+test src/lib.rs - add_two (line 27) ... ok
+test src/lib.rs - add_one (line 11) ... ok
+test src/compute.rs - compute::try_div (line 20) ... ok
+
+test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.51s
+
+Rust provide a very powerful feature for us, that is code navigation in doc comments.
+Add following code to src/lib.rs
:
+#![allow(unused)] +fn main() { +// in lib.rs + +/// Add three to the given value and return a [`Option`] type +pub fn add_three(x: i32) -> Option<i32> { + Some(x + 3) +} +}
Besides jump into the standard library, you can also jump to another module in the package.
++#![allow(unused)] +fn main() { +// in lib.rs + +mod a { + /// Add four to the given value and return a [`Option`] type + /// [`crate::MySpecialFormatter`] + pub fn add_four(x: i32) -> Option<i32> { + Some(x + 4) + } +} + +struct MySpecialFormatter; +}
Below are a few examples of the most common #[doc]
attributes used with rustdoc
.
inline
Used to inline docs, instead of linking out to separate page.
+#[doc(inline)]
+pub use bar::Bar;
+
+/// bar docs
+mod bar {
+ /// the docs for Bar
+ pub struct Bar;
+}
+no_inline
Used to prevent linking out to separate page or anywhere.
+// Example from libcore/prelude
+#[doc(no_inline)]
+pub use crate::mem::drop;
+hidden
Using this tells rustdoc
not to include this in documentation:
// Example from the futures-rs library
+#[doc(hidden)]
+pub use self::async_await::*;
+For documentation, rustdoc
is widely used by the community. It's what is used to generate the std library docs.
The full code of package doc-comments
is here.
The type of array is [T; Length]
, as you can see, array's length is part of their type signature. So their length must be known at compile time.
For example, you cant initialize an array like below:
++#![allow(unused)] +fn main() { +fn init_arr(n: i32) { + let arr = [1; n]; +} +}
This will cause an error, because the compiler has no idea of the exact size of the array at compile time.
+++fn main() { + // Fill the blank with proper array type + let arr: __ = [1, 2, 3, 4, 5]; + + // Modify the code below to make it work + assert!(arr.len() == 4); + + println!("Success!"); +}
++fn main() { + // We can ignore parts of the array type or even the whole type, let the compiler infer it for us + let arr0 = [1, 2, 3]; + let arr: [_; 3] = ['a', 'b', 'c']; + + // Fill the blank + // Arrays are stack allocated, `std::mem::size_of_val` returns the bytes which an array occupies + // A char takes 4 bytes in Rust: Unicode char + assert!(std::mem::size_of_val(&arr) == __); + + println!("Success!"); +}
++fn main() { + // Fill the blank + let list: [i32; 100] = __ ; + + assert!(list[0] == 1); + assert!(list.len() == 100); + + println!("Success!"); +}
++fn main() { + // Fix the error + let _arr = [1, 2, '3']; + + println!("Success!"); +}
++fn main() { + let arr = ['a', 'b', 'c']; + + let ele = arr[1]; // Only modify this line to make the code work! + + assert!(ele == 'a'); + + println!("Success!"); +}
panic
.++// Fix the error +fn main() { + let names = [String::from("Sunfei"), "Sunface".to_string()]; + + // `Get` returns an Option<T>, it's safe to use + let name0 = names.get(0).unwrap(); + + // But indexing is not safe + let _name1 = &names[2]; + + println!("Success!"); +} +
++ + +You can find the solutions here(under the solutions path), but only use it when you need it
+
++// Fix the errors +enum Number { + Zero, + One, + Two, +} + +enum Number1 { + Zero = 0, + One, + Two, +} + +// C-like enum +enum Number2 { + Zero = 0.0, + One = 1.0, + Two = 2.0, +} + + +fn main() { + // An enum variant can be converted to a integer by `as` + assert_eq!(Number::One, Number1::One); + assert_eq!(Number1::One, Number2::One); + + println!("Success!"); +}
++// Fill in the blank +enum Message { + Quit, + Move { x: i32, y: i32 }, + Write(String), + ChangeColor(i32, i32, i32), +} + +fn main() { + let msg1 = Message::Move{__}; // Instantiating with x = 1, y = 2 + let msg2 = Message::Write(__); // Instantiating with "hello, world!" + + println!("Success!"); +}
++// Fill in the blank and fix the error +enum Message { + Quit, + Move { x: i32, y: i32 }, + Write(String), + ChangeColor(i32, i32, i32), +} + +fn main() { + let msg = Message::Move{x: 1, y: 2}; + + if let Message::Move{__} = msg { + assert_eq!(a, b); + } else { + panic!("NEVER LET THIS RUN!"); + } + + println!("Success!"); +}
++// Fill in the blank and fix the errors +enum Message { + Quit, + Move { x: i32, y: i32 }, + Write(String), + ChangeColor(i32, i32, i32), +} + +fn main() { + let msgs: __ = [ + Message::Quit, + Message::Move{x:1, y:3}, + Message::ChangeColor(255,255,0) + ]; + + for msg in msgs { + show_message(msg) + } +} + +fn show_message(msg: Message) { + println!("{}", msg); +}
null
in Rust, we have to use enum Option<T>
to deal with the cases when the value is absent.++// Fill in the blank to make the `println` work. +// Also add some code to prevent the `panic` from running. +fn main() { + let five = Some(5); + let six = plus_one(five); + let none = plus_one(None); + + if let __ = six { + println!("{}", n); + + println!("Success!"); + } + + panic!("NEVER LET THIS RUN!"); +} + +fn plus_one(x: Option<i32>) -> Option<i32> { + match x { + __ => None, + __ => Some(i + 1), + } +}
linked-list
via enums.++use crate::List::*; + +enum List { + // Cons: Tuple struct that wraps an element and a pointer to the next node + Cons(u32, Box<List>), + // Nil: A node that signifies the end of the linked list + Nil, +} + +// Methods can be attached to an enum +impl List { + // Create an empty list + fn new() -> List { + // `Nil` has type `List` + Nil + } + + // Consume a list, and return the same list with a new element at its front + fn prepend(self, elem: u32) -> __ { + // `Cons` also has type List + Cons(elem, Box::new(self)) + } + + // Return the length of the list + fn len(&self) -> u32 { + // `self` has to be matched, because the behavior of this method + // depends on the variant of `self` + // `self` has type `&List`, and `*self` has type `List`, matching on a + // concrete type `T` is preferred over a match on a reference `&T` + // After Rust 2018 you can use self here and tail (with no ref) below as well, + // rust will infer &s and ref tail. + // See https://doc.rust-lang.org/edition-guide/rust-2018/ownership-and-lifetimes/default-match-bindings.html + match *self { + // Can't take ownership of the tail, because `self` is borrowed; + // Instead take a reference to the tail + Cons(_, ref tail) => 1 + tail.len(), + // Base Case: An empty list has zero length + Nil => 0 + } + } + + // Return representation of the list as a (heap allocated) string + fn stringify(&self) -> String { + match *self { + Cons(head, __ tail) => { + // `format!` is similar to `print!`, but returns a heap + // allocated string instead of printing to the console + format!("{}, {}", head, tail.__()) + }, + Nil => { + format!("Nil") + }, + } + } +} + +fn main() { + // Create an empty linked list + let mut list = List::new(); + + // Prepend some elements + list = list.prepend(1); + list = list.prepend(2); + list = list.prepend(3); + + // Show the final state of the list + println!("linked list has length: {}", list.len()); + println!("{}", list.stringify()); +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it
+
Learning resources:
+Slices are similar to arrays, but their length is not known at compile time, so you can't use slice directly.
+[i32]
and str
are slice types, but directly using it will cause errors. You have to use the reference of the slice instead: &[i32]
, &str
.++// Fix the errors, DON'T add new lines! +fn main() { + let arr = [1, 2, 3]; + let s1: [i32] = arr[0..2]; + + let s2: str = "hello, world" as str; + + println!("Success!"); +}
A slice reference is a two-word object, for simplicity reasons, from now on we will use slice instead of slice reference
. The first word is a pointer to the data, and the second word is the length of the slice. The word size is the same as usize, determined by the processor architecture, e.g. 64 bits on an x86-64. Slices can be used to borrow a section of an array, and have the type signature &[T]
.
++fn main() { + let arr: [char; 3] = ['中', '国', '人']; + + let slice = &arr[..2]; + + // Modify '8' to make it work + // TIPS: slice( reference ) IS NOT an array, if it is an array, then `assert!` will be passed: Each of the two chars '中' and '国' occupies 4 bytes, 2 * 4 = 8 + assert!(std::mem::size_of_val(&slice) == 8); + + println!("Success!"); +}
++fn main() { + let arr: [i32; 5] = [1, 2, 3, 4, 5]; + // Fill the blanks to make the code work + let slice: __ = __; + assert_eq!(slice, &[2, 3, 4]); + + println!("Success!"); +}
++fn main() { + let s = String::from("hello"); + + let slice1 = &s[0..2]; + // Fill the blank to make the code work, DON'T USE 0..2 again + let slice2 = &s[__]; + + assert_eq!(slice1, slice2); + + println!("Success!"); +}
++fn main() { + let s = "你好,世界"; + // Modify this line to make the code work + let slice = &s[0..2]; + + assert!(slice == "你"); + + println!("Success!"); +}
&String
can be implicitly converted into &str
.++// Fix errors +fn main() { + let mut s = String::from("hello world"); + + // Here, &s is `&String` type, but `first_letter` needs a `&str` type. + // It works because `&String` can be implicitly converted to `&str. If you want to know more, this is called `Deref coercion`. + let letter = first_letter(&s); + + s.clear(); // error! + + println!("the first letter is: {}", letter); +} +fn first_letter(s: &str) -> &str { + &s[..1] +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it
+
The type of string literal "hello, world"
is &str
, e.g let s: &str = "hello, world"
.
str
type in normal ways, but we can use &str
.++// Fix error without adding new line +fn main() { + let s: str = "hello, world"; + + println!("Success!"); +}
str
by boxing it, &
can be used to convert Box<str>
to &str
++// Fix the error with at least two solutions +fn main() { + let s: Box<str> = "hello, world".into(); + greetings(s) +} + +fn greetings(s: &str) { + println!("{}",s) +}
String
type is defined in std and stored as a vector of bytes (Vec
++// Fill the blank +fn main() { + let mut s = __; + s.push_str("hello, world"); + s.push('!'); + + assert_eq!(s, "hello, world!"); + + println!("Success!"); +}
++// Fix all errors without adding newline +fn main() { + let s = String::from("hello"); + s.push(','); + s.push(" world"); + s += "!".to_string(); + + println!("{}", s); +}
replace
can be used to replace substring++// Fill the blank +fn main() { + let s = String::from("I like dogs"); + // Allocate new memory and store the modified string there + let s1 = s.__("dogs", "cats"); + + assert_eq!(s1, "I like cats"); + + println!("Success!"); +}
More String
methods can be found under String module.
String
with &str
, and String
's ownership can be moved to another variable.++// Fix errors without removing any line +fn main() { + let s1 = String::from("hello,"); + let s2 = String::from("world!"); + let s3 = s1 + s2; + assert_eq!(s3, "hello,world!"); + println!("{}", s1); +}
Opposite to the seldom using of str
, &str
and String
are used everywhere!
&str
can be converted to String
in two ways++// Fix error with at least two solutions +fn main() { + let s = "hello, world"; + greetings(s) +} + +fn greetings(s: String) { + println!("{}", s) +}
String::from
or to_string
to convert a &str
to String
++// Use two approaches to fix the error and without adding a new line +fn main() { + let s = "hello, world".to_string(); + let s1: &str = s; + + println!("Success!"); +}
+fn main() { + // You can use escapes to write bytes by their hexadecimal values + // Fill the blank below to show "I'm writing Rust" + let byte_escape = "I'm writing Ru\x73__!"; + println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape); + + // ...Or Unicode code points. + let unicode_codepoint = "\u{211D}"; + let character_name = "\"DOUBLE-STRUCK CAPITAL R\""; + + println!("Unicode character {} (U+211D) is called {}", + unicode_codepoint, character_name ); + + let long_string = "String literals + can span multiple lines. + The linebreak and indentation here \ + can be escaped too!"; + println!("{}", long_string); +}
++/* Fill in the blank and fix the errors */ +fn main() { + let raw_str = r"Escapes don't work here: \x3F \u{211D}"; + // Modify above line to make it work + assert_eq!(raw_str, "Escapes don't work here: ? ℝ"); + + // If you need quotes in a raw string, add a pair of #s + let quotes = r#"And then I said: "There is no escape!""#; + println!("{}", quotes); + + // If you need "# in your string, just use more #s in the delimiter. + // You can use up to 65535 #s. + let delimiter = r###"A string with "# in it. And even "##!"###; + println!("{}", delimiter); + + // Fill the blank + let long_delimiter = __; + assert_eq!(long_delimiter, "Hello, \"##\""); + + println!("Success!"); +}
Want a string that's not UTF-8? (Remember, str and String must be valid UTF-8). Or maybe you want an array of bytes that's mostly text? Byte strings to the rescue!
+Example:
++use std::str; + +fn main() { + // Note that this is not actually a `&str` + let bytestring: &[u8; 21] = b"this is a byte string"; + + // Byte arrays don't have the `Display` trait, so printing them is a bit limited + println!("A byte string: {:?}", bytestring); + + // Byte strings can have byte escapes... + let escaped = b"\x52\x75\x73\x74 as bytes"; + // ...But no unicode escapes + // let escaped = b"\u{211D} Is not allowed"; + println!("Some escaped bytes: {:?}", escaped); + + + // Raw byte strings work just like raw strings + let raw_bytestring = br"\u{211D} is not escaped here"; + println!("{:?}", raw_bytestring); + + // Converting a byte array to `str` can fail + if let Ok(my_str) = str::from_utf8(raw_bytestring) { + println!("And the same as text: '{}'", my_str); + } + + let _quotes = br#"You can also use "fancier" formatting, \ + like with normal raw strings"#; + + // Byte strings don't have to be UTF-8 + let shift_jis = b"\x82\xe6\x82\xa8\x82\xb1\x82\xbb"; // "ようこそ" In SHIFT-JIS + + // But then they can't always be converted to `str` + match str::from_utf8(shift_jis) { + Ok(my_str) => println!("Conversion successful: '{}'", my_str), + Err(e) => println!("Conversion failed: {:?}", e), + }; +}
A more detailed listing of the ways to write string literals and escape characters is given in the 'Tokens' chapter of the Rust Reference.
+&s1[start..end]
.++fn main() { + let s1 = String::from("hi,中国"); + let h = s1[0]; // Modify this line to fix the error, tips: `h` only takes 1 byte in UTF8 format + assert_eq!(h, "h"); + + let h1 = &s1[3..5]; // Modify this line to fix the error, tips: `中` takes 3 bytes in UTF8 format + assert_eq!(h1, "中"); + + println!("Success!"); +}
++fn main() { + // Fill the blank to print each char in "你好,世界" + for c in "你好,世界".__ { + println!("{}", c) + } +}
You can use utf8_slice to slice UTF8 string, it can index chars instead of bytes.
+Example
++use utf8_slice; +fn main() { + let s = "The 🚀 goes to the 🌑!"; + + let rocket = utf8_slice::slice(s, 4, 5); + // Will equal "🚀" +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it
+
++// Fix the error +struct Person { + name: String, + age: u8, + hobby: String +} +fn main() { + let age = 30; + let p = Person { + name: String::from("sunface"), + age, + }; + + println!("Success!"); +}
++struct Unit; +trait SomeTrait { + // ...Some behaviors defined here. +} + +// We don't care about what fields are in the Unit, but we care about its behaviors. +// So we use a struct with no fields and implement some behaviors for it +impl SomeTrait for Unit { } +fn main() { + let u = Unit; + do_something_with_unit(u); + + println!("Success!"); +} + +// Fill the blank to make the code work +fn do_something_with_unit(u: __) { }
++// Fix the error and fill the blanks +struct Color(i32, i32, i32); +struct Point(i32, i32, i32); +fn main() { + let v = Point(__, __, __); + check_color(v); + + println!("Success!"); +} + +fn check_color(p: Color) { + let (x, _, _) = p; + assert_eq!(x, 0); + assert_eq!(p.1, 127); + assert_eq!(__, 255); + }
++// Fill the blank and fix the error without adding/removing new line +struct Person { + name: String, + age: u8, +} +fn main() { + let age = 18; + let p = Person { + name: String::from("sunface"), + age, + }; + + // How can you believe sunface is only 18? + p.age = 30; + + // Fill the blank + __ = String::from("sunfei"); + + println!("Success!"); +}
++// Fill the blank +struct Person { + name: String, + age: u8, +} +fn main() { + println!("Success!"); +} + +fn build_person(name: String, age: u8) -> Person { + Person { + age, + __ + } +}
++// Fill the blank to make the code work +struct User { + active: bool, + username: String, + email: String, + sign_in_count: u64, +} +fn main() { + let u1 = User { + email: String::from("someone@example.com"), + username: String::from("sunface"), + active: true, + sign_in_count: 1, + }; + + let u2 = set_email(u1); + + println!("Success!"); +} + +fn set_email(u: User) -> User { + User { + email: String::from("contact@im.dev"), + __ + } +}
#[derive(Debug)]
to make a struct printable.++// Fill the blanks to make the code work +#[__] +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let scale = 2; + let rect1 = Rectangle { + width: dbg!(30 * scale), // Print debug info to stderr and assign the value of `30 * scale` to `width` + height: 50, + }; + + dbg!(&rect1); // Print debug info to stderr + + println!(__, rect1); // Print debug info to stdout +}
Within the destructuring of a single variable, both by-move and by-reference pattern bindings can be used at the same time. Doing this will result in a partial move of the variable, which means that parts of the variable will be moved while other parts stay. In such a case, the parent variable cannot be used afterwards as a whole, however the parts that are only referenced (and not moved) can still be used.
+++fn main() { + #[derive(Debug)] + struct Person { + name: String, + age: Box<u8>, + } + + let person = Person { + name: String::from("Alice"), + age: Box::new(20), + }; + + // `name` is moved out of person, but `age` is referenced + let Person { name, ref age } = person; + + println!("The person's age is {}", age); + + println!("The person's name is {}", name); + + // Error! borrow of partially moved value: `person` partial move occurs + //println!("The person struct is {:?}", person); + + // `person` cannot be used but `person.age` can be used as it is not moved + println!("The person's age from person struct is {}", person.age); +}
++// Fix errors to make it work +#[derive(Debug)] +struct File { + name: String, + data: String, +} +fn main() { + let f = File { + name: String::from("readme.md"), + data: "Rust By Practice".to_string() + }; + + let _name = f.name; + + // ONLY modify this line + println!("{}, {}, {:?}",f.name, f.data, f); +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it
+
(T1, T2, ...)
, where T1
, T2
are the types of tuple's members.++fn main() { + let _t0: (u8,i16) = (0, -1); + // Tuples can be tuple's members + let _t1: (u8, (i16, u32)) = (0, (-1, 1)); + // Fill the blanks to make the code work + let t: (u8, __, i64, __, __) = (1u8, 2u16, 3i64, "hello", String::from(", world")); + + println!("Success!"); +}
++// Make it work +fn main() { + let t = ("i", "am", "sunface"); + assert_eq!(t.1, "sunface"); + + println!("Success!"); +}
++// Fix the error +fn main() { + let too_long_tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13); + println!("too long tuple: {:?}", too_long_tuple); +}
++fn main() { + let tup = (1, 6.4, "hello"); + + // Fill the blank to make the code work + let __ = tup; + + assert_eq!(x, 1); + assert_eq!(y, "hello"); + assert_eq!(z, 6.4); + + println!("Success!"); +}
+fn main() { + let (x, y, z); + + // Fill the blank + __ = (1, 2, 3); + + assert_eq!(x, 3); + assert_eq!(y, 1); + assert_eq!(z, 2); + + println!("Success!"); +}
++fn main() { + // Fill the blank, need a few computations here. + let (x, y) = sum_multiply(__); + + assert_eq!(x, 5); + assert_eq!(y, 6); + + println!("Success!"); +} + +fn sum_multiply(nums: (i32, i32)) -> (i32, i32) { + (nums.0 + nums.1, nums.0 * nums.1) +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it
+
A package is a project which you create with Cargo (in most cases), so it contains a Cargo.toml
file in it.
.
+├── Cargo.toml
+└── src
+ └── main.rs
+
+1 directory, 2 files
+
+# in Cargo.toml
+[package]
+name = "hello-package"
+version = "0.1.0"
+edition = "2021"
+
+++Note! We will use this package across the whole chapter as a practice project.
+
.
+├── Cargo.toml
+└── src
+ └── lib.rs
+
+1 directory, 2 files
+
+# in Cargo.toml
+[package]
+name = "hello-package1"
+version = "0.1.0"
+edition = "2021"
+
+++Note! This package could be safely removed due to the first one's existence.
+
+/* FILL in the blank with your ANSWER */ + +// Q: What's the difference between package number 1 and number 2? +// A: __
A crate is a binary or library. The crate root is a source file that the Rust compiler starts from and makes up the root module of the crate.
+In package hello-package
, there is binary crate with the same name as the package : hello-package
, and src/main.rs
is the crate root of this binary crate.
Similar to hello-package
, hello-package1
also has a crate in it, however, this package doesn't contain a binary crate but a library crate, and src/lib.rs
is the crate root.
+/* FILL in the blank with your ANSWER */ + +// Q: What's the name of the library crate in package `hello-package1`? +// A: __
hello-package
and describe it's files tree below:# FILL in the blanks
+.
+├── Cargo.lock
+├── Cargo.toml
+├── src
+│ ├── __
+│ └── __
+
+After this step, there should be two crates in package hello-package
: a binary crate and a library crate, both with the same name as the package.
src/bin
directory: each file will be a separate binary crate with the same name as the file.# Create a package which contains
+# 1. three binary crates: `hello-package`, `main1` and `main2`
+# 2. one library crate
+# describe the directory tree below
+.
+├── Cargo.toml
+├── Cargo.lock
+├── src
+│ ├── __
+│ ├── __
+│ └── __
+│ └── __
+│ └── __
+├── tests # directory for integrated tests files
+│ └── some_integration_tests.rs
+├── benches # dir for benchmark files
+│ └── simple_bench.rs
+└── examples # dir for example files
+ └── simple_example.rs
+
+Yep, as you can see, the above package structure is very standard and is widely used in many Rust projects.
+++ + +You can find the solutions here (under the solutions path), but only use it when you need it :)
+
Learning resources:
+Modules let us organize the code within a crate into groups for readability and ease of reuse. Module also controls the privacy of items, which is whether an item can be seen by outside code( public ), or is just an internal implementation and not available for outside code( private ).
+We have created a package named hello-package
in previous chapter, and it looks like this:
.
+├── Cargo.toml
+├── src
+│ ├── lib.rs
+│ └── main.rs
+
+Now it's time to create some modules in the library crate and use them in the binary crate, let's start.
+front_of_house
based on the module tree below:library crate root
+ └── front_of_house
+ ├── hosting
+ │ ├── add_to_waitlist
+ │ └── seat_at_table
+ └── serving
+ ├── take_order
+ ├── serve_order
+ ├── take_payment
+ └── complain
+
++// FILL in the blank +// in __.rs + +mod front_of_house { + // IMPLEMENT this module.. +}
add_to_waitlist
from a function eat_at_restaurant
which is within the library crate root.+// In lib.rs + +// FILL in the blanks and FIX the errors +// You need to make something public with `pub` to provide accessibility for outside code `fn eat_at_restaurant()` +mod front_of_house { + /* ...snip... */ +} + +pub fn eat_at_restaurant() { + // Call add_to_waitlist with **absolute path**: + __.add_to_waitlist(); + + // Call with **relative path** + __.add_to_waitlist(); +}
super
to import items within the parent module+// In lib.rs + +mod back_of_house { + fn fix_incorrect_order() { + cook_order(); + // FILL in the blank in three ways + //1. using keyword `super` + //2. using absolute path + __.serve_order(); + } + + fn cook_order() {} +}
+// In lib.rs +pub mod front_of_house { + pub mod hosting { + pub fn add_to_waitlist() {} + + pub fn seat_at_table() -> String { + String::from("sit down please") + } + } + + pub mod serving { + pub fn take_order() {} + + pub fn serve_order() {} + + pub fn take_payment() {} + + // Maybe you don't want the guest hearing the your complaining about them + // So just make it private + fn complain() {} + } +} + +pub fn eat_at_restaurant() -> String { + front_of_house::hosting::add_to_waitlist(); + + back_of_house::cook_order(); + + String::from("yummy yummy!") +} + +pub mod back_of_house { + pub fn fix_incorrect_order() { + cook_order(); + crate::front_of_house::serving::serve_order(); + } + + pub fn cook_order() {} +}
.
+├── Cargo.toml
+├── src
+│ ├── back_of_house.rs
+│ ├── front_of_house
+│ │ ├── hosting.rs
+│ │ ├── mod.rs
+│ │ └── serving.rs
+│ ├── lib.rs
+│ └── main.rs
+
++// In src/lib.rs + +// IMPLEMENT...
+// In src/back_of_house.rs + +// IMPLEMENT...
+// In src/front_of_house/mod.rs + +// IMPLEMENT...
+// In src/front_of_house/hosting.rs + +// IMPLEMENT...
+// In src/front_of_house/serving.rs + +// IMPLEMENT...
Please ensure you have completed the 4th exercise before making further progress.
+You should have below structures and the corresponding codes in them when reaching here:
+.
+├── Cargo.toml
+├── src
+│ ├── back_of_house.rs
+│ ├── front_of_house
+│ │ ├── hosting.rs
+│ │ ├── mod.rs
+│ │ └── serving.rs
+│ ├── lib.rs
+│ └── main.rs
+
++// In src/main.rs + +// FILL in the blank and FIX the errors +fn main() { + assert_eq!(__, "sit down please"); + assert_eq!(__,"yummy yummy!"); +}
++ + +You can find the solutions here (under the solutions path), but only use it when you need it :)
+
as
keyword.+use std::fmt::Result; +use std::io::Result; + +fn main() {}
++// FILL in the blank in two ways +// DON'T add new code line +use std::collections::__; + +fn main() { + let _c1:HashMap<&str, i32> = HashMap::new(); + let mut c2 = BTreeMap::new(); + c2.insert(1, "a"); + let _c3: HashSet<i32> = HashSet::new(); +}
pub use
hello-package
, add something to make the below code work+fn main() { + assert_eq!(hello_package::hosting::seat_at_table(), "sit down please"); + assert_eq!(hello_package::eat_at_restaurant(),"yummy yummy!"); +}
Sometimes we want an item only be public to a certain crate. For this we can use the pub(in Crate)
syntax.
+pub mod a { + pub const I: i32 = 3; + + fn semisecret(x: i32) -> i32 { + use self::b::c::J; + x + J + } + + pub fn bar(z: i32) -> i32 { + semisecret(I) * z + } + pub fn foo(y: i32) -> i32 { + semisecret(I) + y + } + + mod b { + pub(in crate::a) mod c { + pub(in crate::a) const J: i32 = 4; + } + } +}
The full code of hello-package
is here.
++ + +You can find the solutions here (under the solutions path), but only use it when you need it :)
+
Following questions come up weekly in online Rust discussions:
+The answers to these questions are always Practice: doing some exercises, and then reading some small and excellent Rust projects.
+This is precisely the goal of this book, so, collecting relative resourses and representing in Rust By Practice seems not a bad idea.
+Answers for above questions usually came with ripgrep
, though I don't think it is a small project, but yes, go for it if you are not afraid to delve deep a bit.
Tutorial https://www.flenker.blog/hecto/
will lead you to build a text editor from scratch.
Ncspot, a terminal Spotify client. Small, simple, well organized and async, it's good for learning.
+This project is for the book Command-Line Rust(O'Reily)
, it will show you how to write small CLIs (clones of head
, cat
, ls
).
This book will guide you to make a command line program that lets you hide secret messages in PNG files. The primary goal here is to get you writing code. The secondary goal is to get you reading documentation.
+This blog series creates a small operating system in the Rust programming language. Each post is a small tutorial and includes all needed code, so you can follow along if you like. The source code is also available in the corresponding Github repository.
+On CodeCrafters, you can recreate your favorite developer tools from scratch. It's a hands-on, minimally-guided approach to master Rust, while appreciating the internals and documentation of popular technology that we use every day.
+mini-redis is an incomplete Redis client and server implementation using tokio, it has decent code base and detail explanations, very suitable for learning Rust and asynchronous programming.
+This online book will walk through the basics of interpreted language implementation in Rust with a focus on the challenges that are specific to using Rust.
+To be continued...
+ + ++// FIX the error without removing any code line +struct test { + list: Vec<i32>, + a: i32 +} + +impl test { + pub fn new() -> Self { + test { list:vec![1,2,3,4,5,6,7], a:0 } + } + + pub fn run(&mut self) { + for i in self.list.iter() { + self.do_something(*i) + } + + } + + pub fn do_something(&mut self, n: i32) { + self.a = n; + } +} + +fn main() {}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it :)
+
Fighting with compiler is very common in our daily coding, especially for those unfamiliar with Rust.
+This chapter will provide some exercises to help us avoid such cases to lower the steep learning curve.
+ + +++// Fill in the blanks +fn main() { + let n = 5; + + if n < 0 { + println!("{} is negative", n); + } __ n > 0 { + println!("{} is positive", n); + } __ { + println!("{} is zero", n); + } +}
If/else
expression can be used in assignments.++// Fix the errors +fn main() { + let n = 5; + + let big_n = + if n < 10 && n > -10 { + println!(", and is a small number, increase ten-fold"); + + 10 * n + } else { + println!(", and is a big number, halve the number"); + + n / 2.0 ; + } + + println!("{} -> {}", n, big_n); +}
for in
construct can be used to iterate through an Iterator, e.g a range a..b
.++fn main() { + for n in 1..=100 { // modify this line to make the code work + if n == 100 { + panic!("NEVER LET THIS RUN") + } + } + + println!("Success!"); +}
++// Fix the errors without adding or removing lines +fn main() { + let names = [String::from("liming"),String::from("hanmeimei")]; + for name in names { + // Do something with name... + } + + println!("{:?}", names); + + let numbers = [1, 2, 3]; + // The elements in numbers are Copy,so there is no move here + for n in numbers { + // Do something with n... + } + + println!("{:?}", numbers); +}
+fn main() { + let a = [4, 3, 2, 1]; + + // Iterate the indexing and value in 'a' + for (i,v) in a.__ { + println!("The {}th element is {}",i+1,v); + } +}
while
keyword can be used to run a loop when a condition is true.++// Fill in the blanks to make the last println! work ! +fn main() { + // A counter variable + let mut n = 1; + + // Loop while the condition is true + while n __ 10 { + if n % 15 == 0 { + println!("fizzbuzz"); + } else if n % 3 == 0 { + println!("fizz"); + } else if n % 5 == 0 { + println!("buzz"); + } else { + println!("{}", n); + } + + + __; + } + + println!("n reached {}, so loop is over",n); +}
break
to break the loop.++// Fill in the blank +fn main() { + let mut n = 0; + for i in 0..=100 { + if n == 66 { + __ + } + n += 1; + } + + assert_eq!(n, 66); + + println!("Success!"); +}
continue
will skip over the remaining code in current iteration and go to the next iteration.++// Fill in the blanks +fn main() { + let mut n = 0; + for i in 0..=100 { + if n != 66 { + n+=1; + __; + } + + __ + } + + assert_eq!(n, 66); + + println!("Success!"); +}
break
or continue
.++// Fill in the blanks +fn main() { + let mut count = 0u32; + + println!("Let's count until infinity!"); + + // Infinite loop + loop { + count += 1; + + if count == 3 { + println!("three"); + + // Skip the rest of this iteration + __; + } + + println!("{}", count); + + if count == 5 { + println!("OK, that's enough"); + + __; + } + } + + assert_eq!(count, 5); + + println!("Success!"); +}
break
to return a value++// Fill in the blank +fn main() { + let mut counter = 0; + + let result = loop { + counter += 1; + + if counter == 10 { + __; + } + }; + + assert_eq!(result, 20); + + println!("Success!"); +}
++// Fill in the blank +fn main() { + let mut count = 0; + 'outer: loop { + 'inner1: loop { + if count >= 20 { + // This would break only the inner1 loop + break 'inner1; // `break` is also works. + } + count += 2; + } + + count += 5; + + 'inner2: loop { + if count >= 30 { + // This breaks the outer loop + break 'outer; + } + + // This will continue the outer loop + continue 'outer; + } + } + + assert!(count == __); + + println!("Success!"); +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it
+
All types which want to be printable must implement the std::fmt
formatting trait: std::fmt::Debug
or std::fmt::Display
.
Automatic implementations are only provided for types such as in the std
library. All others have to be manually implemented.
The implementation of Debug
is very straightforward: All types can derive
the std::fmt::Debug
implementation. This is not true for std::fmt::Display
which must be manually implemented.
{:?}
must be used to print out the type which has implemented the Debug
trait.
+#![allow(unused)] +fn main() { +// This structure cannot be printed either with `fmt::Display` or +// with `fmt::Debug`. +struct UnPrintable(i32); + +// To make this struct printable with `fmt::Debug`, we can derive the automatic implementations provided by Rust +#[derive(Debug)] +struct DebugPrintable(i32); +}
++/* Fill in the blanks and Fix the errors */ +struct Structure(i32); + +fn main() { + // Types in std and Rust have implemented the fmt::Debug trait + println!("__ months in a year.", 12); + + println!("Now __ will print!", Structure(3)); +}
fmt::Debug
definitely makes one type printable, but sacrifices some elegance. Maybe we can get more elegant by replacing {:?}
with something else( but not {}
!) +#[derive(Debug)] +struct Person { + name: String, + age: u8 +} + +fn main() { + let person = Person { name: "Sunface".to_string(), age: 18 }; + + /* Make it output: + Person { + name: "Sunface", + age: 18, + } + */ + println!("{:?}", person); +}
Debug
trait for our types++#[derive(Debug)] +struct Structure(i32); + +#[derive(Debug)] +struct Deep(Structure); + + +fn main() { + // The problem with `derive` is there is no control over how + // the results look. What if I want this to just show a `7`? + + /* Make it print: Now 7 will print! */ + println!("Now {:?} will print!", Deep(Structure(7))); +}
Yeah, Debug
is simple and easy to use. But sometimes we want to customize the output appearance of our type. This is where Display
really shines.
Unlike Debug
, there is no way to derive the implementation of the Display
trait, we have to manually implement it.
Another thing to note: the placeholder for Display
is {}
not {:?}
.
++/* Make it work*/ +use std::fmt; + +struct Point2D { + x: f64, + y: f64, +} + +impl fmt::Display for Point2D { + /* Implement.. */ +} + +impl fmt::Debug for Point2D { + /* Implement.. */ +} + +fn main() { + let point = Point2D { x: 3.3, y: 7.2 }; + assert_eq!(format!("{}",point), "Display: 3.3 + 7.2i"); + assert_eq!(format!("{:?}",point), "Debug: Complex { real: 3.3, imag: 7.2 }"); + + println!("Success!"); +}
?
operatorImplementing fmt::Display
for a structure whose elements must be handled separately is tricky. The problem is each write!
generates a fmt::Result
which must be handled in the same place.
Fortunately, Rust provides the ?
operator to help us eliminate some unnecessary codes for dealing with fmt::Result
.
++/* Make it work */ +use std::fmt; + +struct List(Vec<i32>); + +impl fmt::Display for List { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // Extract the value using tuple indexing, + // and create a reference to `vec`. + let vec = &self.0; + + write!(f, "[")?; + + // Iterate over `v` in `vec` while enumerating the iteration + // count in `count`. + for (count, v) in vec.iter().enumerate() { + // For every element except the first, add a comma. + // Use the ? operator to return on errors. + if count != 0 { write!(f, ", ")?; } + write!(f, "{}", v)?; + } + + // Close the opened bracket and return a fmt::Result value. + write!(f, "]") + } +} + +fn main() { + let v = List(vec![1, 2, 3]); + assert_eq!(format!("{}",v), "[0: 1, 1: 2, 2: 3]"); + println!("Success!"); +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it :)
+
1.🌟🌟
++/* Fill in the blanks */ +fn main() { + println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob"); // => Alice, this is Bob. Bob, this is Alice + assert_eq!(format!("{1}{0}", 1, 2), __); + assert_eq!(format!(__, 1, 2), "2112"); + println!("Success!"); +}
2.🌟🌟
++fn main() { + println!("{argument}", argument = "test"); // => "test" + + /* Fill in the blanks */ + assert_eq!(format!("{name}{}", 1, __), "21"); + assert_eq!(format!(__,a = "a", b = 'b', c = 3 ), "a 3 b"); + + /* Fix the error */ + // Named argument must be placed after other arguments + println!("{abc} {1}", abc = "def", 2); + + println!("Success!"); +}
3.🌟🌟 By default, you can pad string with spaces
++fn main() { + // The following two are padding with 5 spaces + println!("Hello {:5}!", "x"); // => "Hello x !" + println!("Hello {:1$}!", "x", 5); // => "Hello x !" + + /* Fill in the blanks */ + assert_eq!(format!("Hello __!", 5, "x"), "Hello x !"); + assert_eq!(format!("Hello __!", "x", width = 5), "Hello x !"); + + println!("Success!"); +}
4.🌟🌟🌟 Left align, right align, pad with specified characters.
++fn main() { + // Left align + println!("Hello {:<5}!", "x"); // => Hello x ! + // Right align + assert_eq!(format!("Hello __!", "x"), "Hello x!"); + // Center align + assert_eq!(format!("Hello __!", "x"), "Hello x !"); + + // Left align, pad with '&' + assert_eq!(format!("Hello {:&<5}!", "x"), __); + + println!("Success!"); +}
5.🌟🌟 You can pad numbers with extra zeros.
++fn main() { + println!("Hello {:5}!", 5); // => Hello 5! + println!("Hello {:+}!", 5); // => Hello +5! + println!("Hello {:05}!", 5); // => Hello 00005! + println!("Hello {:05}!", -5); // => Hello -0005! + + /* Fill in the blank */ + assert!(format!("{number:0>width$}", number=1, width=6) == __); + + println!("Success!") +;}
6.🌟🌟 Floating point precision
+++/* Fill in the blanks */ +fn main() { + let v = 3.1415926; + + println!("{:.1$}", v, 4); // same as {:.4} => 3.1416 + + assert_eq!(format!("__", v), "3.14"); + assert_eq!(format!("__", v), "+3.14"); + assert_eq!(format!("__", v), "3"); + + println!("Success!"); +}
7.🌟🌟🌟 String length
++fn main() { + let s = "Hello, world!"; + + println!("{0:.5}", s); // => Hello + + assert_eq!(format!("Hello __!", 3, "abcdefg"), "Hello abc!"); + + println!("Success!"); +}
8.🌟🌟
++fn main() { + assert_eq!(format!("__", 27), "0b11011"); + assert_eq!(format!("__", 27), "0o33"); + assert_eq!(format!("__", 27), "0x1b"); + assert_eq!(format!("__", 27), "0x1B"); + + println!("{:x}!", 27); // Hex with no prefix => 1b + + println!("{:#010b}", 27); // Pad binary with 0, width = 10, => 0b00011011 + + println!("Success!"); +}
9.🌟🌟🌟
++fn get_person() -> String { + String::from("sunface") +} + +fn get_format() -> (usize, usize) { + (4, 1) +} + + +fn main() { + let person = get_person(); + println!("Hello, {person}!"); + + let (width, precision) = get_format(); + let scores = [("sunface", 99.12), ("jack", 60.34)]; + /* Make it print: + sunface: 99.1 + jack: 60.3 + */ + for (name, score) in scores { + println!("{name}: __"); + } +}
Example
++fn main() { + // Exponent + println!("{:2e}", 1000000000); // => 1e9 + println!("{:2E}", 1000000000); // => 1E9 + + // Pointer address + let v= vec![1, 2, 3]; + println!("{:p}", v.as_ptr()); // => 0x600002324050 + + // Escape + println!("Hello {{}}"); // => Hello {} +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it :)
+
+fn main() { + // In general, the `{}` will be automatically replaced with any + // arguments. These will be stringified. + println!("{} days", 31); + + // Without a suffix, 31 becomes an i32. You can change what type 31 is + // by providing a suffix. The number 31i64 for example has the type i64. + + // There are various optional patterns this works with. Positional + // arguments can be used. + println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob"); + + // As can named arguments. + println!("{subject} {verb} {object}", + object="the lazy dog", + subject="the quick brown fox", + verb="jumps over"); + + // Special formatting can be specified after a `:`. + println!("{} of {:b} people know binary, the other half doesn't", 1, 2); + + // You can right-align text with a specified width. This will output + // " 1". 5 white spaces and a "1". + println!("{number:>width$}", number=1, width=6); + + // You can pad numbers with extra zeroes. This will output "000001". + println!("{number:0>width$}", number=1, width=6); + + // Rust even checks to make sure the correct number of arguments are + // used. + println!("My name is {0}, {1} {0}", "Bond"); + // FIXME ^ Add the missing argument: "James" + + // Create a structure named `Structure` which contains an `i32`. + #[allow(dead_code)] + struct Structure(i32); + + // However, custom types such as this structure require more complicated + // handling. This will not work. + println!("This struct `{}` won't print...", Structure(3)); + // FIXME ^ Comment out this line. + + // For Rust 1.58 and above, you can directly capture the argument from + // surrounding variable. Just like the above, this will output + // " 1". 5 white spaces and a "1". + let number: f64 = 1.0; + let width: usize = 6; + println!("{number:>width$}"); +}
[std::fmt
][fmt] contains many [traits
][traits] which govern the display
+of text. The base form of two important ones are listed below:
fmt::Debug
: Uses the {:?}
marker. Format text for debugging purposes.fmt::Display
: Uses the {}
marker. Format text in a more elegant, user
+friendly fashion.Here, we used fmt::Display
because the std library provides implementations
+for these types. To print text for custom types, more steps are required.
Implementing the fmt::Display
trait automatically implements the
+[ToString
] trait which allows us to [convert] the type to [String
][string].
Printing is handled by a series of [macros
][macros] defined in [std::fmt
][fmt]
+Some of which include:
format!
: write formatted text to [String
][string]print!
: same as format!
but the text is printed to the console (io::stdout).println!
: same as print!
but a newline is appended.eprint!
: same as format!
but the text is printed to the standard error (io::stderr).eprintln!
: same as eprint!
but a newline is appended.All parse text in the same fashion. As a plus, Rust checks format correctness at compile time.
+format!
1.🌟
+++fn main() { + let s1 = "hello"; + /* Fill in the blank */ + let s = format!(__); + assert_eq!(s, "hello, world!"); +}
print!
, println!
2.🌟
+++fn main() { + /* Fill in the blanks to make it print: + Hello world, I am + Sunface! + */ + __("hello world, "); + __("I am"); + __("Sunface!"); +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it :)
+
Closures can capture the enclosing environments. For example we can capture the x
variable :
+fn main() { + let x = 1; + let closure = |val| val + x; + assert_eq!(closure(2), 3); +}
From the syntax, we can see that closures are very convenient for on the fly usage. Unlike functions, both the input and return types of a closure can be inferred by the compiler.
++fn main() { + // Increment via closures and functions. + fn function(i: i32) -> i32 { i + 1 } + + // Closures are anonymous, here we are binding them to references + // + // These nameless functions are assigned to appropriately named variables. + let closure_annotated = |i: i32| -> i32 { i + 1 }; + let closure_inferred = |i | i + 1 ; + + let i = 1; + // Call the function and closures. + println!("function: {}", function(i)); + println!("closure_annotated: {}", closure_annotated(i)); + println!("closure_inferred: {}", closure_inferred(i)); + + // A closure taking no arguments which returns an `i32`. + // The return type is inferred. + let one = || 1; + println!("closure returning one: {}", one()); + +}
Closures can capture variables by borrowing or moving. But they prefer to capture by borrowing and only go lower when required:
+&T
&mut T
T
+/* Make it work with least amount of changes*/ +fn main() { + let color = String::from("green"); + + let print = move || println!("`color`: {}", color); + + print(); + print(); + + // `color` can be borrowed immutably again, because the closure only holds + // an immutable reference to `color`. + let _reborrow = &color; + + println!("{}",color); +}
+/* Make it work +- Dont use `_reborrow` and `_count_reborrowed` +- Dont modify `assert_eq` +*/ +fn main() { + let mut count = 0; + + let mut inc = || { + count += 1; + println!("`count`: {}", count); + }; + + inc(); + + + let _reborrow = &count; + + inc(); + + // The closure no longer needs to borrow `&mut count`. Therefore, it is + // possible to reborrow without an error + let _count_reborrowed = &mut count; + + assert_eq!(count, 0); +}
+/* Make it work in two ways, none of them is to remove `take(movable)` away from the code +*/ +fn main() { + let movable = Box::new(3); + + let consume = || { + println!("`movable`: {:?}", movable); + take(movable); + }; + + consume(); + consume(); +} + +fn take<T>(_v: T) {}
For comparison, the following code has no error:
++fn main() { + let movable = Box::new(3); + + let consume = move || { + println!("`movable`: {:?}", movable); + }; + + consume(); + consume(); +}
The following four closures has no difference in input and return types.
++#![allow(unused)] +fn main() { +fn add_one_v1 (x: u32) -> u32 { x + 1 } +let add_one_v2 = |x: u32| -> u32 { x + 1 }; +let add_one_v3 = |x| { x + 1 }; +let add_one_v4 = |x| x + 1 ; +}
+fn main() { + let example_closure = |x| x; + + let s = example_closure(String::from("hello")); + + /* Make it work, only change the following line */ + let n = example_closure(5); +}
When taking a closure as an input parameter, the closure's complete type must be annotated using one of the following traits:
++/* Make it work by changing the trait bound, in two ways*/ +fn fn_once<F>(func: F) +where + F: FnOnce(usize) -> bool, +{ + println!("{}", func(3)); + println!("{}", func(4)); +} + +fn main() { + let x = vec![1, 2, 3]; + fn_once(|z|{z == x.len()}) +}
+fn main() { + let mut s = String::new(); + + let update_string = |str| s.push_str(str); + + exec(update_string); + + println!("{:?}",s); +} + +/* Fill in the blank */ +fn exec<'a, F: __>(mut f: F) { + f("hello") +}
On a variable-by-variable basis, the compiler will capture variables in the least restrictive manner possible.
+For instance, consider a parameter annotated as FnOnce. This specifies that the closure may capture by &T
, &mut T
, or T
, but the compiler will ultimately choose based on how the captured variables are used in the closure.
+Which trait to use is determined by what the closure does with captured value.
This is because if a move is possible, then any type of borrow should also be possible. Note that the reverse is not true. If the parameter is annotated as Fn
, then capturing variables by &mut T
or T
are not allowed.
+/* Fill in the blank */ + +// A function which takes a closure as an argument and calls it. +// <F> denotes that F is a "Generic type parameter" +fn apply<F>(f: F) where + // The closure takes no input and returns nothing. + F: __ { + + f(); +} + +// A function which takes a closure and returns an `i32`. +fn apply_to_3<F>(f: F) -> i32 where + // The closure takes an `i32` and returns an `i32`. + F: Fn(i32) -> i32 { + + f(3) +} + +fn main() { + use std::mem; + + let greeting = "hello"; + // A non-copy type. + // `to_owned` creates owned data from borrowed one + let mut farewell = "goodbye".to_owned(); + + // Capture 2 variables: `greeting` by reference and + // `farewell` by value. + let diary = || { + // `greeting` is by reference: requires `Fn`. + println!("I said {}.", greeting); + + // Mutation forces `farewell` to be captured by + // mutable reference. Now requires `FnMut`. + farewell.push_str("!!!"); + println!("Then I screamed {}.", farewell); + println!("Now I can sleep. zzzzz"); + + // Manually calling drop forces `farewell` to + // be captured by value. Now requires `FnOnce`. + mem::drop(farewell); + }; + + // Call the function which applies the closure. + apply(diary); + + // `double` satisfies `apply_to_3`'s trait bound + let double = |x| 2 * x; + + println!("3 doubled: {}", apply_to_3(double)); +}
Move closures may still implement Fn
or FnMut
, even though they capture variables by move. This is because the traits implemented by a closure type are determined by what the closure does with captured values, not how it captures them. The move
keyword only specifies the latter.
+fn main() { + let s = String::new(); + + let update_string = move || println!("{}",s); + + exec(update_string); +} + +fn exec<F: FnOnce()>(f: F) { + f() +}
The following code also has no error:
++fn main() { + let s = String::new(); + + let update_string = move || println!("{}",s); + + exec(update_string); +} + +fn exec<F: Fn()>(f: F) { + f() +}
+/* Fill in the blank */ +fn main() { + let mut s = String::new(); + + let update_string = |str| -> String {s.push_str(str); s }; + + exec(update_string); +} + +fn exec<'a, F: __>(mut f: F) { + f("hello"); +}
Since closure can be used as arguments, you might wonder can we use functions as arguments too? And indeed we can.
+++/* Implement `call_me` to make it work */ +fn call_me { + f(); +} + +fn function() { + println!("I'm a function!"); +} + +fn main() { + let closure = || println!("I'm a closure!"); + + call_me(closure); + call_me(function); +}
Returning a closure is much harder than you may have thought of.
++/* Fill in the blank using two approaches, + and fix the error */ +fn create_fn() -> __ { + let num = 5; + + // How does the following closure capture the environment variable `num` + // &T, &mut T, T ? + |x| x + num +} + + +fn main() { + let fn_plain = create_fn(); + fn_plain(1); +}
+/* Fill in the blank and fix the error*/ +fn factory(x:i32) -> __ { + + let num = 5; + + if x > 1{ + move |x| x + num + } else { + move |x| x + num + } +}
Example
++struct Cacher<T,E> +where + T: Fn(E) -> E, + E: Copy +{ + query: T, + value: Option<E>, +} + +impl<T,E> Cacher<T,E> +where + T: Fn(E) -> E, + E: Copy +{ + fn new(query: T) -> Cacher<T,E> { + Cacher { + query, + value: None, + } + } + + fn value(&mut self, arg: E) -> E { + match self.value { + Some(v) => v, + None => { + let v = (self.query)(arg); + self.value = Some(v); + v + } + } + } +} +fn main() { + +} + +#[test] +fn call_with_different_values() { + let mut c = Cacher::new(|a| a); + + let v1 = c.value(1); + let v2 = c.value(2); + + assert_eq!(v2, 1); +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it :)
+
Learning resources:
+The iterator pattern allows us to perform some tasks on a sequence of items in turn. An iterator is responsible for the logic of iterating over each item and determining when the sequence has finished.
++fn main() { + let v = vec![1, 2, 3]; + for x in v { + println!("{}",x) + } +}
In the code above, You may consider for
as a simple loop, but actually it is iterating over a iterator.
By default for
will apply the into_iter
to the collection, and change it into a iterator. As a result, the following code is equivalent to previous one:
+fn main() { + let v = vec![1, 2, 3]; + for x in v.into_iter() { + println!("{}",x) + } +}
+/* Refactoring the following code using iterators */ +fn main() { + let arr = [0; 10]; + for i in 0..arr.len() { + println!("{}",arr[i]); + } +}
a..b
.+/* Fill in the blank */ +fn main() { + let mut v = Vec::new(); + for n in __ { + v.push(n); + } + + assert_eq!(v.len(), 100); +}
All iterators implement a trait named Iterator
that is defined in the standard library:
+#![allow(unused)] +fn main() { +pub trait Iterator { + type Item; + + fn next(&mut self) -> Option<Self::Item>; + + // Methods with default implementations elided +} +}
And we can call the next
method on iterators directly.
+/* Fill the blanks and fix the errors. +Using two ways if possible */ +fn main() { + let v1 = vec![1, 2]; + + assert_eq!(v1.next(), __); + assert_eq!(v1.next(), __); + assert_eq!(v1.next(), __); +}
In the previous section, we have mentioned that for
will apply the into_iter
to the collection, and change it into a iterator. However, this is not the only way to convert collections into iterators.
into_iter
, iter
, iter_mut
, all of them can convert a collection into iterator, but in different ways.
into_iter
consumes the collection, once the collection has been consumed, it is no longer available for reuse, because its ownership has been moved within the loop.iter
, this borrows each element of the collection through each iteration, thus leaving the collection untouched and available for reuse after the loopiter_mut
, this mutably borrows each element of the collection, allowing for the collection to be modified in place.+/* Make it work */ +fn main() { + let arr = vec![0; 10]; + for i in arr { + println!("{}", i); + } + + println!("{:?}",arr); +}
+/* Fill in the blank */ +fn main() { + let mut names = vec!["Bob", "Frank", "Ferris"]; + + for name in names.__{ + *name = match name { + &mut "Ferris" => "There is a rustacean among us!", + _ => "Hello", + } + } + + println!("names: {:?}", names); +}
+/* Fill in the blank */ +fn main() { + let mut values = vec![1, 2, 3]; + let mut values_iter = values.__; + + if let Some(v) = values_iter.__{ + __ + } + + assert_eq!(values, vec![0, 2, 3]); +}
We can not only create iterators from collection's types, but also can create iterators by implementing the Iterator
trait on our own types.
Example
++struct Counter { + count: u32, +} + +impl Counter { + fn new() -> Counter { + Counter { count: 0 } + } +} + +impl Iterator for Counter { + type Item = u32; + + fn next(&mut self) -> Option<Self::Item> { + if self.count < 5 { + self.count += 1; + Some(self.count) + } else { + None + } + } +} + +fn main() { + let mut counter = Counter::new(); + + assert_eq!(counter.next(), Some(1)); + assert_eq!(counter.next(), Some(2)); + assert_eq!(counter.next(), Some(3)); + assert_eq!(counter.next(), Some(4)); + assert_eq!(counter.next(), Some(5)); + assert_eq!(counter.next(), None); +}
+struct Fibonacci { + curr: u32, + next: u32, +} + +// Implement `Iterator` for `Fibonacci`. +// The `Iterator` trait only requires a method to be defined for the `next` element. +impl Iterator for Fibonacci { + // We can refer to this type using Self::Item + type Item = u32; + + /* Implement next method */ + fn next(&mut self) +} + +// Returns a Fibonacci sequence generator +fn fibonacci() -> Fibonacci { + Fibonacci { curr: 0, next: 1 } +} + +fn main() { + let mut fib = fibonacci(); + assert_eq!(fib.next(), Some(1)); + assert_eq!(fib.next(), Some(1)); + assert_eq!(fib.next(), Some(2)); + assert_eq!(fib.next(), Some(3)); + assert_eq!(fib.next(), Some(5)); +}
The Iterator
trait has a number of methods with default implementations provided by the standard library.
Some of these methods call the method next
to use up the iterator, so they are called consuming adaptors.
++/* Fill in the blank and fix the errors */ +fn main() { + let v1 = vec![1, 2, 3]; + + let v1_iter = v1.iter(); + + // The sum method will take the ownership of the iterator and iterates through the items by repeatedly calling next method + let total = v1_iter.sum(); + + assert_eq!(total, __); + + println!("{:?}, {:?}",v1, v1_iter); +}
Other than converting a collection into an iterator, we can also collect
the result values into a collection, collect
will consume the iterator.
+/* Make it work */ +use std::collections::HashMap; +fn main() { + let names = [("sunface",18), ("sunfei",18)]; + let folks: HashMap<_, _> = names.into_iter().collect(); + + println!("{:?}",folks); + + let v1: Vec<i32> = vec![1, 2, 3]; + + let v2 = v1.iter().collect(); + + assert_eq!(v2, vec![1, 2, 3]); +}
Methods allowing you to change one iterator into another iterator are known as iterator adaptors. You can chain multiple iterator adaptors to perform complex actions in a readable way.
+But because all iterators are lazy, you have to call one of the consuming adapters to get results from calls to iterator adapters.
++/* Fill in the blanks */ +fn main() { + let v1: Vec<i32> = vec![1, 2, 3]; + + let v2: Vec<_> = v1.iter().__.__; + + assert_eq!(v2, vec![2, 3, 4]); +}
+/* Fill in the blanks */ +use std::collections::HashMap; +fn main() { + let names = ["sunface", "sunfei"]; + let ages = [18, 18]; + let folks: HashMap<_, _> = names.into_iter().__.collect(); + + println!("{:?}",folks); +}
+/* Fill in the blanks */ +#[derive(PartialEq, Debug)] +struct Shoe { + size: u32, + style: String, +} + +fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> { + shoes.into_iter().__.collect() +} + +fn main() { + let shoes = vec![ + Shoe { + size: 10, + style: String::from("sneaker"), + }, + Shoe { + size: 13, + style: String::from("sandal"), + }, + Shoe { + size: 10, + style: String::from("boot"), + }, + ]; + + let in_my_size = shoes_in_size(shoes, 10); + + assert_eq!( + in_my_size, + vec![ + Shoe { + size: 10, + style: String::from("sneaker") + }, + Shoe { + size: 10, + style: String::from("boot") + }, + ] + ); +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it :)
+
The use of "Associated types" improves the overall readability of code by moving inner types locally into a trait as output types. For example :
++#![allow(unused)] +fn main() { +pub trait CacheableItem: Clone + Default + fmt::Debug + Decodable + Encodable { + type Address: AsRef<[u8]> + Clone + fmt::Debug + Eq + Hash; + fn is_null(&self) -> bool; +} +}
Using of Address
is much more clearer and convenient than AsRef<[u8]> + Clone + fmt::Debug + Eq + Hash
.
++struct Container(i32, i32); + +// USING associated types to re-implement trait Contains. +// trait Contains { +// type A; +// type B; + +trait Contains<A, B> { + fn contains(&self, _: &A, _: &B) -> bool; + fn first(&self) -> i32; + fn last(&self) -> i32; +} + +impl Contains<i32, i32> for Container { + fn contains(&self, number_1: &i32, number_2: &i32) -> bool { + (&self.0 == number_1) && (&self.1 == number_2) + } + // Grab the first number. + fn first(&self) -> i32 { self.0 } + + // Grab the last number. + fn last(&self) -> i32 { self.1 } +} + +fn difference<A, B, C: Contains<A, B>>(container: &C) -> i32 { + container.last() - container.first() +} + +fn main() { + let number_1 = 3; + let number_2 = 10; + + let container = Container(number_1, number_2); + + println!("Does container contain {} and {}: {}", + &number_1, &number_2, + container.contains(&number_1, &number_2)); + println!("First number: {}", container.first()); + println!("Last number: {}", container.last()); + + println!("The difference is: {}", difference(&container)); +}
When we use generic type parameters, we can specify a default concrete type for the generic type. This eliminates the need for implementors of the trait to specify a concrete type if the default type works.
+++use std::ops::Sub; + +#[derive(Debug, PartialEq)] +struct Point<T> { + x: T, + y: T, +} + +// FILL in the blank in three ways: two of them use the default generic parameters, the other one not. +// Notice that the implementation uses the associated type `Output`. +impl __ { + type Output = Self; + + fn sub(self, other: Self) -> Self::Output { + Point { + x: self.x - other.x, + y: self.y - other.y, + } + } +} + +fn main() { + assert_eq!(Point { x: 2, y: 3 } - Point { x: 1, y: 0 }, + Point { x: 1, y: 3 }); + + println!("Success!"); +}
Nothing in Rust prevents a trait from having a method with the same name as another trait’s method, nor does Rust prevent you from implementing both traits on one type. It’s also possible to implement a method directly on the type with the same name as methods from traits.
+When calling methods with the same name, we have to use Fully Qualified Syntax.
++trait UsernameWidget { + // Get the selected username out of this widget + fn get(&self) -> String; +} + +trait AgeWidget { + // Get the selected age out of this widget + fn get(&self) -> u8; +} + +// A form with both a UsernameWidget and an AgeWidget. +struct Form { + username: String, + age: u8, +} + +impl UsernameWidget for Form { + fn get(&self) -> String { + self.username.clone() + } +} + +impl AgeWidget for Form { + fn get(&self) -> u8 { + self.age + } +} + +fn main() { + let form = Form{ + username: "rustacean".to_owned(), + age: 28, + }; + + // If you uncomment this line, you'll get an error saying + // "multiple `get` found". Because, after all, there are multiple methods + // named `get`. + // println!("{}", form.get()); + + let username = UsernameWidget::get(&form); + assert_eq!("rustacean".to_owned(), username); + let age = AgeWidget::get(&form); // You can also use `<Form as AgeWidget>::get` + assert_eq!(28, age); + + println!("Success!"); +}
+trait Pilot { + fn fly(&self) -> String; +} + +trait Wizard { + fn fly(&self) -> String; +} + +struct Human; + +impl Pilot for Human { + fn fly(&self) -> String { + String::from("This is your captain speaking.") + } +} + +impl Wizard for Human { + fn fly(&self) -> String { + String::from("Up!") + } +} + +impl Human { + fn fly(&self) -> String { + String::from("*waving arms furiously*") + } +} + +fn main() { + let person = Human; + + assert_eq!(__, "This is your captain speaking."); + assert_eq!(__, "Up!"); + + assert_eq!(__, "*waving arms furiously*"); + + println!("Success!"); +}
Sometimes, you might need one trait to use another trait’s functionality( like the "inheritance" in other languages ). In this case, you need to rely on the dependent trait also being implemented. The trait you rely on is a supertrait
of the trait you’re implementing.
++trait Person { + fn name(&self) -> String; +} + +// Person is a supertrait of Student. +// Implementing Student requires you to also impl Person. +trait Student: Person { + fn university(&self) -> String; +} + +trait Programmer { + fn fav_language(&self) -> String; +} + +// CompSciStudent (computer science student) is a subtrait of both Programmer +// and Student. Implementing CompSciStudent requires you to impl both supertraits. +trait CompSciStudent: Programmer + Student { + fn git_username(&self) -> String; +} + +fn comp_sci_student_greeting(student: &dyn CompSciStudent) -> String { + format!( + "My name is {} and I attend {}. My favorite language is {}. My Git username is {}", + student.name(), + student.university(), + student.fav_language(), + student.git_username() + ) +} + +struct CSStudent { + name: String, + university: String, + fav_language: String, + git_username: String +} + +// IMPLEMENT the necessary traits for CSStudent to make the code work +impl ... + +fn main() { + let student = CSStudent { + name: "Sunfei".to_string(), + university: "XXX".to_string(), + fav_language: "Rust".to_string(), + git_username: "sunface".to_string() + }; + + // FILL in the blank + println!("{}", comp_sci_student_greeting(__)); +}
We can’t implement external traits on external types. For example, we can’t implement the Display
trait on Vec<T>
within our own crate, because Display
and Vec<T>
are defined in the standard library and aren’t local to our crate.
This restriction is often called the orphan rule, so named because the parent type is not present. This rule ensures that other people’s code can’t break your code and vice versa.
+It’s possible to get around this restriction using the newtype pattern, which involves creating a new type in a tuple struct.
++use std::fmt; + +// DEFINE a newtype `Pretty` here + + +impl fmt::Display for Pretty { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "\"{}\"", self.0.clone() + ", world") + } +} + +fn main() { + let w = Pretty("hello".to_string()); + println!("w = {}", w); +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it :)
+
Const generics are generic arguments that range over constant values, rather than types or lifetimes. This allows, for instance, types to be parameterized by integers. In fact, there has been one example of const generic types since early on in Rust's development: the array types [T; N], for some type T and N: usize. However, there has previously been no way to abstract over arrays of an arbitrary size: if you wanted to implement a trait for arrays of any size, you would have to do so manually for each possible value. For a long time, even the standard library methods for arrays were limited to arrays of length at most 32 due to this problem.
++struct ArrayPair<T, const N: usize> { + left: [T; N], + right: [T; N], +} + +impl<T: Debug, const N: usize> Debug for ArrayPair<T, N> { + // ... +}
+fn foo<const N: usize>() {} + +fn bar<T, const M: usize>() { + foo::<M>(); // Okay: `M` is a const parameter + foo::<2021>(); // Okay: `2021` is a literal + foo::<{20 * 100 + 20 * 10 + 1}>(); // Okay: const expression contains no generic parameters + + foo::<{ M + 1 }>(); // Error: const expression contains the generic parameter `M` + foo::<{ std::mem::size_of::<T>() }>(); // Error: const expression contains the generic parameter `T` + + let _: [u8; M]; // Okay: `M` is a const parameter + let _: [u8; std::mem::size_of::<T>()]; // Error: const expression contains the generic parameter `T` +} + +fn main() {}
+/// A region of memory containing at least `N` `T`s. +pub struct MinSlice<T, const N: usize> { + /// The bounded region of memory. Exactly `N` `T`s. + pub head: [T; N], + /// Zero or more remaining `T`s after the `N` in the bounded region. + pub tail: [T], +} + +fn main() { + let slice: &[u8] = b"Hello, world"; + let reference: Option<&u8> = slice.get(6); + // We know this value is `Some(b' ')`, + // but the compiler can't know that. + assert!(reference.is_some()); + + let slice: &[u8] = b"Hello, world"; + // Length check is performed when we construct a MinSlice, + // and it's known at compile time to be of length 12. + // If the `unwrap()` succeeds, no more checks are needed + // throughout the `MinSlice`'s lifetime. + let minslice = MinSlice::<u8, 12>::from_slice(slice).unwrap(); + let value: u8 = minslice.head[6]; + assert_eq!(value, b' ') +}
<T, const N: usize>
is part of the struct type, it means Array<i32, 3>
and Array<i32, 4>
are different types.+struct Array<T, const N: usize> { + data : [T; N] +} + +fn main() { + let arrays = [ + Array{ + data: [1, 2, 3], + }, + Array { + data: [1.0, 2.0, 3.0], + }, + Array { + data: [1, 2] + } + ]; + + println!("Success!"); +}
++// Fill in the blanks to make it work. +fn print_array<__>(__) { + println!("{:?}", arr); +} +fn main() { + let arr = [1, 2, 3]; + print_array(arr); + + let arr = ["hello", "world"]; + print_array(arr); +}
const expressions
will fit your needs.+#![allow(incomplete_features)] +#![feature(generic_const_exprs)] + +fn check_size<T>(val: T) +where + Assert<{ core::mem::size_of::<T>() < 768 }>: IsTrue, +{ + //... +} + +// Fix the errors in main. +fn main() { + check_size([0u8; 767]); + check_size([0i32; 191]); + check_size(["hello你好"; __]); // Size of &str ? + check_size([(); __].map(|_| "hello你好".to_string())); // Size of String? + check_size(['中'; __]); // Size of char ? + + println!("Success!"); +} + + + +pub enum Assert<const CHECK: bool> {} + +pub trait IsTrue {} + +impl IsTrue for Assert<true> {}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it :)
+
++// Fill in the blanks to make it work +struct A; // Concrete type `A`. +struct S(A); // Concrete type `S`. +struct SGen<T>(T); // Generic type `SGen`. + +fn reg_fn(_s: S) {} + +fn gen_spec_t(_s: SGen<A>) {} + +fn gen_spec_i32(_s: SGen<i32>) {} + +fn generic<T>(_s: SGen<T>) {} + +fn main() { + // Using the non-generic functions + reg_fn(__); // Concrete type. + gen_spec_t(__); // Implicitly specified type parameter `A`. + gen_spec_i32(__); // Implicitly specified type parameter `i32`. + + // Explicitly specified type parameter `char` to `generic()`. + generic::<char>(__); + + // Implicitly specified type parameter `char` to `generic()`. + generic(__); + + println!("Success!"); +}
fun::<A, B, ...>()
.++// Implement the generic function below. +fn sum + +fn main() { + assert_eq!(5, sum(2i8, 3i8)); + assert_eq!(50, sum(20, 30)); + assert_eq!(2.46, sum(1.23, 1.23)); + + println!("Success!"); +}
impl
++// Implement struct Point to make it work. + + +fn main() { + let integer = Point { x: 5, y: 10 }; + let float = Point { x: 1.0, y: 4.0 }; + + println!("Success!"); +}
++// Modify this struct to make the code work +struct Point<T> { + x: T, + y: T, +} + +fn main() { + // DON'T modify this code. + let p = Point{x: 5, y : "hello".to_string()}; + + println!("Success!"); +}
++// Add generic for Val to make the code work, DON'T modify the code in `main`. +struct Val { + val: f64, +} + +impl Val { + fn value(&self) -> &f64 { + &self.val + } +} + + +fn main() { + let x = Val{ val: 3.0 }; + let y = Val{ val: "hello".to_string()}; + println!("{}, {}", x.value(), y.value()); +}
+struct Point<T, U> { + x: T, + y: U, +} + +impl<T, U> Point<T, U> { + // Implement mixup to make it work, DON'T modify other code. + fn mixup +} + +fn main() { + let p1 = Point { x: 5, y: 10 }; + let p2 = Point { x: "Hello", y: '中'}; + + let p3 = p1.mixup(p2); + + assert_eq!(p3.x, 5); + assert_eq!(p3.y, '中'); + + println!("Success!"); +}
++// Fix the errors to make the code work. +struct Point<T> { + x: T, + y: T, +} + +impl Point<f32> { + fn distance_from_origin(&self) -> f32 { + (self.x.powi(2) + self.y.powi(2)).sqrt() + } +} + +fn main() { + let p = Point{x: 5, y: 10}; + println!("{}",p.distance_from_origin()); +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it
+
Learning resources:
+In traits chapter we have seen that we can't use impl Trait
when returning multiple types.
Another limitation of arrays is that they can only store elements of one type. Using enums is not a bad solution when we have a fixed set of types at compile time, but trait objects would be more flexible and powerful.
+The Rust compiler needs to know how much space a function's return type requires. Because the different implementations of a trait probably uses different amounts of memory, functions need to either return a concrete type or the same type when using impl Trait
, or return a trait object with dyn
.
++trait Bird { + fn quack(&self) -> String; +} + +struct Duck; +impl Duck { + fn swim(&self) { + println!("Look, the duck is swimming") + } +} +struct Swan; +impl Swan { + fn fly(&self) { + println!("Look, the duck.. oh sorry, the swan is flying") + } +} + +impl Bird for Duck { + fn quack(&self) -> String{ + "duck duck".to_string() + } +} + +impl Bird for Swan { + fn quack(&self) -> String{ + "swan swan".to_string() + } +} + +fn main() { + // FILL in the blank. + let duck = __; + duck.swim(); + + let bird = hatch_a_bird(2); + // This bird has forgotten how to swim, so below line will cause an error. + // bird.swim(); + // But it can quak. + assert_eq!(bird.quack(), "duck duck"); + + let bird = hatch_a_bird(1); + // This bird has forgotten how to fly, so below line will cause an error. + // bird.fly(); + // But it can quak too. + assert_eq!(bird.quack(), "swan swan"); + + println!("Success!"); +} + +// IMPLEMENT this function. +fn hatch_a_bird... +
+trait Bird { + fn quack(&self); +} + +struct Duck; +impl Duck { + fn fly(&self) { + println!("Look, the duck is flying") + } +} +struct Swan; +impl Swan { + fn fly(&self) { + println!("Look, the duck.. oh sorry, the swan is flying") + } +} + +impl Bird for Duck { + fn quack(&self) { + println!("{}", "duck duck"); + } +} + +impl Bird for Swan { + fn quack(&self) { + println!("{}", "swan swan"); + } +} + +fn main() { + // FILL in the blank to make the code work. + let birds __; + + for bird in birds { + bird.quack(); + // When duck and swan turn into Birds, they all forgot how to fly, only remember how to quack. + // So, the code below will cause an error. + // bird.fly(); + } +}
&dyn
and Box<dyn>
++// FILL in the blanks. +trait Draw { + fn draw(&self) -> String; +} + +impl Draw for u8 { + fn draw(&self) -> String { + format!("u8: {}", *self) + } +} + +impl Draw for f64 { + fn draw(&self) -> String { + format!("f64: {}", *self) + } +} + +fn main() { + let x = 1.1f64; + let y = 8u8; + + // Draw x. + draw_with_box(__); + + // Draw y. + draw_with_ref(&y); + + println!("Success!"); +} + +fn draw_with_box(x: Box<dyn Draw>) { + x.draw(); +} + +fn draw_with_ref(x: __) { + x.draw(); +}
When we use trait bounds on generics, the compiler generates nongeneric implementations of functions and methods for each concrete type that we use in place of a generic type parameter. The code that results from monomorphization is doing static dispatch, which is when the compiler knows what method you’re calling at compile time.
+When we use trait objects, Rust must use dynamic dispatch. The compiler doesn’t know all the types that might be used with the code that is using trait objects, so it doesn’t know which method implemented on which type to call. Instead, at runtime, Rust uses the pointers inside the trait object to know which method to call. There is a runtime cost when this lookup happens that doesn’t occur with static dispatch. Dynamic dispatch also prevents the compiler from choosing to inline a method’s code, which in turn prevents some optimizations.
+However, we do get extra flexibility when using dynamic dispatch.
+++trait Foo { + fn method(&self) -> String; +} + +impl Foo for u8 { + fn method(&self) -> String { format!("u8: {}", *self) } +} + +impl Foo for String { + fn method(&self) -> String { format!("string: {}", *self) } +} + +// IMPLEMENT below with generics. +fn static_dispatch... + +// Implement below with trait objects. +fn dynamic_dispatch... + +fn main() { + let x = 5u8; + let y = "Hello".to_string(); + + static_dispatch(x); + dynamic_dispatch(&y); + + println!("Success!"); +}
You can only make object-safe traits into trait objects. A trait is object safe if all the methods defined in the trait have the following properties:
+Self
.++// Use at least two approaches to make it work. +// DON'T add/remove any code line. +trait MyTrait { + fn f(&self) -> Self; +} + +impl MyTrait for u32 { + fn f(&self) -> Self { 42 } +} + +impl MyTrait for String { + fn f(&self) -> Self { self.clone() } +} + +fn my_function(x: Box<dyn MyTrait>) { + x.f() +} + +fn main() { + my_function(Box::new(13_u32)); + my_function(Box::new(String::from("abc"))); + + println!("Success!"); +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it :)
+
A trait tells the Rust compiler about functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way. We can use trait bounds to specify that a generic type can be any type that has certain behavior.
+++Note: Traits are similar to interfaces in other languages, although with some differences.
+
++struct Sheep { naked: bool, name: String } + +trait Animal { + // Associated function signature; `Self` refers to the implementor type. + fn new(name: String) -> Self; + + // Method signatures; these will return a string. + fn name(&self) -> String; + + fn noise(&self) -> String; + + // Traits can provide default method definitions. + fn talk(&self) { + println!("{} says {}", self.name(), self.noise()); + } +} + +impl Sheep { + fn is_naked(&self) -> bool { + self.naked + } + + fn shear(&mut self) { + if self.is_naked() { + // Implementor methods can use the implementor's trait methods. + println!("{} is already naked...", self.name()); + } else { + println!("{} gets a haircut!", self.name); + + self.naked = true; + } + } +} + +// Implement the `Animal` trait for `Sheep`. +impl Animal for Sheep { + // `Self` is the implementor type: `Sheep`. + fn new(name: String) -> Sheep { + Sheep { name: name, naked: false } + } + + fn name(&self) -> String { + self.name.clone() + } + + fn noise(&self) -> String { + if self.is_naked() { + "baaaaah?".to_string() + } else { + "baaaaah!".to_string() + } + } + + // Default trait methods can be overridden. + fn talk(&self) { + // For example, we can add some quiet contemplation. + println!("{} pauses briefly... {}", self.name, self.noise()); + } +} + +fn main() { + // Type annotation is necessary in this case. + let mut dolly: Sheep = Animal::new("Dolly".to_string()); + // TODO ^ Try removing the type annotations. + + dolly.talk(); + dolly.shear(); + dolly.talk(); +}
++// Fill in the two impl blocks to make the code work. +// DON'T modify the code in `main`. +trait Hello { + fn say_hi(&self) -> String { + String::from("hi") + } + + fn say_something(&self) -> String; +} + +struct Student {} +impl Hello for Student { +} +struct Teacher {} +impl Hello for Teacher { +} + +fn main() { + let s = Student {}; + assert_eq!(s.say_hi(), "hi"); + assert_eq!(s.say_something(), "I'm a good student"); + + let t = Teacher {}; + assert_eq!(t.say_hi(), "Hi, I'm your new teacher"); + assert_eq!(t.say_something(), "I'm not a bad teacher"); + + println!("Success!"); +}
The compiler is capable of providing basic implementations for some traits via
+the #[derive]
attribute. For more info, please visit here.
++// `Centimeters`, a tuple struct that can be compared +#[derive(PartialEq, PartialOrd)] +struct Centimeters(f64); + +// `Inches`, a tuple struct that can be printed +#[derive(Debug)] +struct Inches(i32); + +impl Inches { + fn to_centimeters(&self) -> Centimeters { + let &Inches(inches) = self; + + Centimeters(inches as f64 * 2.54) + } +} + +// ADD some attributes to make the code work! +// DON'T modify other code! +struct Seconds(i32); + +fn main() { + let _one_second = Seconds(1); + + println!("One second looks like: {:?}", _one_second); + let _this_is_true = (_one_second == _one_second); + let _this_is_false = (_one_second > _one_second); + + let foot = Inches(12); + + println!("One foot equals {:?}", foot); + + let meter = Centimeters(100.0); + + let cmp = + if foot.to_centimeters() < meter { + "smaller" + } else { + "bigger" + }; + + println!("One foot is {} than one meter.", cmp); +}
In Rust, many of the operators can be overloaded via traits. That is, some operators can be used to accomplish different tasks based on their input arguments. This is possible because operators are syntactic sugar for method calls. For example, the + operator in a + b calls the add method (as in a.add(b)). This add method is part of the Add trait. Hence, the + operator can be used by any implementor of the Add trait.
+++use std::ops; + +// Implement fn multiply to make the code work. +// As mentioned above, `+` needs `T` to implement `std::ops::Add` Trait. +// So, what about `*`? You can find the answer here: https://doc.rust-lang.org/core/ops/ +fn multipl + +fn main() { + assert_eq!(6, multiply(2u8, 3u8)); + assert_eq!(5.0, multiply(1.0, 5.0)); + + println!("Success!"); +}
++// Fix the errors, DON'T modify the code in `main`. +use std::ops; + +struct Foo; +struct Bar; + +struct FooBar; + +struct BarFoo; + +// The `std::ops::Add` trait is used to specify the functionality of `+`. +// Here, we make `Add<Bar>` - the trait for addition with a RHS of type `Bar`. +// The following block implements the operation: Foo + Bar = FooBar +impl ops::Add<Bar> for Foo { + type Output = FooBar; + + fn add(self, _rhs: Bar) -> FooBar { + FooBar + } +} + +impl ops::Sub<Foo> for Bar { + type Output = BarFoo; + + fn sub(self, _rhs: Foo) -> BarFoo { + BarFoo + } +} + +fn main() { + // DON'T modify the code below. + // You need to derive some trait for FooBar to make it comparable. + assert_eq!(Foo + Bar, FooBar); + assert_eq!(Foo - Bar, BarFoo); + + println!("Success!"); +}
Instead of a concrete type for the item parameter, we specify the impl keyword and the trait name. This parameter accepts any type that implements the specified trait.
+++// Implement `fn summary` to make the code work. +// Fix the errors without removing any code line +trait Summary { + fn summarize(&self) -> String; +} + +#[derive(Debug)] +struct Post { + title: String, + author: String, + content: String, +} + +impl Summary for Post { + fn summarize(&self) -> String { + format!("The author of post {} is {}", self.title, self.author) + } +} + +#[derive(Debug)] +struct Weibo { + username: String, + content: String, +} + +impl Summary for Weibo { + fn summarize(&self) -> String { + format!("{} published a weibo {}", self.username, self.content) + } +} + +fn main() { + let post = Post { + title: "Popular Rust".to_string(), + author: "Sunface".to_string(), + content: "Rust is awesome!".to_string(), + }; + let weibo = Weibo { + username: "sunface".to_string(), + content: "Weibo seems to be worse than Tweet".to_string(), + }; + + summary(post); + summary(weibo); + + println!("{:?}", post); + println!("{:?}", weibo); +} + +// Implement `fn summary` below. +
We can also use the impl Trait syntax in the return position to return a value of some type that implements a trait.
+However, you can only use impl Trait if you’re returning a single type, use Trait Objects instead when you really need to return several types.
+++struct Sheep {} +struct Cow {} + +trait Animal { + fn noise(&self) -> String; +} + +impl Animal for Sheep { + fn noise(&self) -> String { + "baaaaah!".to_string() + } +} + +impl Animal for Cow { + fn noise(&self) -> String { + "moooooo!".to_string() + } +} + +// Returns some struct that implements Animal, but we don't know which one at compile time. +// FIX the errors here, you can make a fake random, or you can use trait object. +fn random_animal(random_number: f64) -> impl Animal { + if random_number < 0.5 { + Sheep {} + } else { + Cow {} + } +} + +fn main() { + let random_number = 0.234; + let animal = random_animal(random_number); + println!("You've randomly chosen an animal, and it says {}", animal.noise()); +}
The impl Trait
syntax works for straightforward cases but is actually syntax sugar for a longer form, which is called a trait bound.
When working with generics, the type parameters often must use traits as bounds to stipulate what functionality a type implements.
++fn main() { + assert_eq!(sum(1, 2), 3); +} + +// Implement `fn sum` with trait bound in two ways. +fn sum<T>(x: T, y: T) -> T { + x + y +}
++// FIX the errors. +struct Pair<T> { + x: T, + y: T, +} + +impl<T> Pair<T> { + fn new(x: T, y: T) -> Self { + Self { + x, + y, + } + } +} + +impl<T: std::fmt::Debug + PartialOrd> Pair<T> { + fn cmp_display(&self) { + if self.x >= self.y { + println!("The largest member is x = {:?}", self.x); + } else { + println!("The largest member is y = {:?}", self.y); + } + } +} + +struct Unit(i32); + +fn main() { + let pair = Pair{ + x: Unit(1), + y: Unit(3) + }; + + pair.cmp_display(); +}
++// Fill in the blanks to make it work +fn example1() { + // `T: Trait` is the commonly used way. + // `T: Fn(u32) -> u32` specifies that we can only pass a closure to `T`. + struct Cacher<T: Fn(u32) -> u32> { + calculation: T, + value: Option<u32>, + } + + impl<T: Fn(u32) -> u32> Cacher<T> { + fn new(calculation: T) -> Cacher<T> { + Cacher { + calculation, + value: None, + } + } + + fn value(&mut self, arg: u32) -> u32 { + match self.value { + Some(v) => v, + None => { + let v = (self.calculation)(arg); + self.value = Some(v); + v + }, + } + } + } + + let mut cacher = Cacher::new(|x| x+1); + assert_eq!(cacher.value(10), __); + assert_eq!(cacher.value(15), __); +} + + +fn example2() { + // We can also use `where` to construct `T` + struct Cacher<T> + where T: Fn(u32) -> u32, + { + calculation: T, + value: Option<u32>, + } + + impl<T> Cacher<T> + where T: Fn(u32) -> u32, + { + fn new(calculation: T) -> Cacher<T> { + Cacher { + calculation, + value: None, + } + } + + fn value(&mut self, arg: u32) -> u32 { + match self.value { + Some(v) => v, + None => { + let v = (self.calculation)(arg); + self.value = Some(v); + v + }, + } + } + } + + let mut cacher = Cacher::new(|x| x+1); + assert_eq!(cacher.value(20), __); + assert_eq!(cacher.value(25), __); +} + + + +fn main() { + example1(); + example2(); + + println!("Success!"); +}
++ + +You can find the solutions here(under the solutions path), but only use it when you need it :)
+
Practice Rust with challenging examples, exercises and projects
+ +This book was designed for easily diving into and getting skilled with Rust, and it's very easy to use: All you need to do is to make each exercise compile without ERRORS and Panics !
+We use mdbook building our exercises. You can run locally with below steps:
+$ git clone git@github.com:sunface/rust-by-practice.git
+$ cargo install mdbook
+$ cd rust-by-practice && mdbook serve en/
+
+Part of our examples and exercises are borrowed from Rust By Example, thanks for your great works!
+Although they are so awesome, we have our own secret weapons :)
+There are three parts in each chapter: examples, exercises and practices
+Besides examples, we have a lot of exercises
, you can Read, Edit and Run them ONLINE
Covering nearly all aspects of Rust, such as async/await, threads, sync primitives, optimizing, standard libraries, tool chain, data structures and algorithms etc.
+Every exercise has its own solutions
+The overall difficulties are a bit higher and from easy to super hard: easy 🌟 medium 🌟🌟 hard 🌟🌟🌟 super hard 🌟🌟🌟🌟
+What we want to do is fill in the gap between learning and getting started with real projects.
+ + +