From c9ce0d80d07c6795300f0f7b938cd5f94cd8df63 Mon Sep 17 00:00:00 2001 From: sunface Date: Tue, 8 Mar 2022 16:30:50 +0800 Subject: [PATCH] update zh/traits.md --- zh-CN/src/SUMMARY.md | 3 +- zh-CN/src/collections/String.md | 209 +++++++++ zh-CN/src/collections/hashmap.md | 219 +++++++++ zh-CN/src/collections/intro.md | 5 +- zh-CN/src/collections/vector.md | 243 ++++++++++ zh-CN/src/generics-traits/advance-traits.md | 1 - zh-CN/src/generics-traits/advanced-traits.md | 280 ++++++++++- zh-CN/src/generics-traits/trait-object.md | 228 +++++++++ zh-CN/src/generics-traits/traits.md | 470 +++++++++++++++++++ 9 files changed, 1654 insertions(+), 4 deletions(-) create mode 100644 zh-CN/src/collections/String.md delete mode 100644 zh-CN/src/generics-traits/advance-traits.md diff --git a/zh-CN/src/SUMMARY.md b/zh-CN/src/SUMMARY.md index 7b8e3c7..a569883 100644 --- a/zh-CN/src/SUMMARY.md +++ b/zh-CN/src/SUMMARY.md @@ -28,7 +28,8 @@ - [特征 Traits](generics-traits/traits.md) - [特征对象](generics-traits/trait-object.md) - [进一步深入特征](generics-traits/advanced-traits.md) -- [集合类型 todo](collections/intro.md) +- [集合类型](collections/intro.md) + - [动态字符串 String](collections/String.md) - [动态数组 Vector](collections/vector.md) - [KV 存储 HashMap](collections/hashmap.md) - [类型转换 todo](type-conversion.md) diff --git a/zh-CN/src/collections/String.md b/zh-CN/src/collections/String.md new file mode 100644 index 0000000..f65c736 --- /dev/null +++ b/zh-CN/src/collections/String.md @@ -0,0 +1,209 @@ +# String +`std::string::String` is a UTF-8 encoded, growable string. It is the most common string type we used in daily dev, it also has ownership over the string contents. + +### Basic operations +1. 🌟🌟 +```rust,editable + +// FILL in the blanks and FIX errors +// 1. Don't use `to_string()` +// 2. Dont'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) +} +``` + +### String and &str +A `String` is stored as a vector of bytes (`Vec`), 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`. + +2. 🌟🌟 +```rust,editable +// FILL in the blanks +fn main() { + // get a slice of String with reference: String -> &str + 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!") +} +``` + +3. 🌟🌟 +```rust,editable + +// Question: how many heap allocations are happend 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!") +} +``` + +### UTF-8 & Indexing +Strings are always valid UTF-8. This has a few implications: + +- the first of which is that if you need a non-UTF-8 string, consider [OsString](https://doc.rust-lang.org/stable/std/ffi/struct.OsString.html). It is similar, but without the UTF-8 constraint. +- The second implication is that you cannot index into a String + +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. + +4. 🌟🌟🌟 You can't use index to access a char in a string, but you can use slice `&s1[start..end]`. + +```rust,editable + +// 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 all chars in s + for (i, c) in s.__ { + if i == 7 { + assert_eq!(c, '世') + } + } + + println!("Success!") +} +``` + + +#### utf8_slice +You can use [utf8_slice](https://docs.rs/utf8_slice/1.0.0/utf8_slice/fn.slice.html) to slice UTF8 string, it can index chars instead of bytes. + +**Example** +```rust +use utf_slice; +fn main() { + let s = "The 🚀 goes to the 🌑!"; + + let rocket = utf8_slice::slice(s, 4, 5); + // Will equal "🚀" +} +``` + + +5. 🌟🌟🌟 +> Tips: maybe you need `from_utf8` method + +```rust,editable + +// 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 bytes vector into a String + // We know these bytes are valid, so we'll use `unwrap()`. + let s1 = __; + + + assert_eq!(s, s1); + + println!("Success!") +} +``` + +### Representation +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. + +6. 🌟🌟 If a String has enough capacity, adding elements to it will not re-allocate +```rust,editable + +// 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!") +} +``` + +7. 🌟🌟🌟 +```rust,editable + +// FILL in the blanks +use std::mem; + +fn main() { + let story = String::from("Rust By Practice"); + + // Prevent automatically dropping the String's data + let mut story = mem::ManuallyDrop::new(story); + + let ptr = story.__(); + let len = story.__(); + let capacity = story.__(); + + // story has nineteen bytes + assert_eq!(16, len); + + // We can re-build 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!") +} +``` + + +### Common methods +More exercises of String methods can be found [here](../std/String.md). + +> You can find the solutions [here](https://github.com/sunface/rust-by-practice)(under the solutions path), but only use it when you need it \ No newline at end of file diff --git a/zh-CN/src/collections/hashmap.md b/zh-CN/src/collections/hashmap.md index 96e549d..b5619b3 100644 --- a/zh-CN/src/collections/hashmap.md +++ b/zh-CN/src/collections/hashmap.md @@ -1 +1,220 @@ # HashMap +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](https://abseil.io/blog/20180927-swisstables). The original C++ version of SwissTable can be found [here](https://github.com/abseil/abseil-cpp/blob/master/absl/container/internal/raw_hash_set.h), and this [CppCon talk](https://www.youtube.com/watch?v=ncHmEUmJZf4) gives an overview of how the algorithm works. + + +### Basic Operations +1. 🌟🌟 + +```rust,editbale +// FILL in the blanks and FIX the erros +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 a Option<&V> + let score = scores.get("Sunface"); + assert_eq!(score, Some(98)); + + if scores.contains_key("Daniel") { + // indexing return 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) + } +} +``` + +2. 🌟🌟 +```rust,editable + +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!") +} +``` + +3. 🌟🌟 +```rust,editable + +// 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 +} +``` + +### Requirements of HashMap key +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 thereof +- `String` 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](https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems) 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` will implement `Hash` if `T`implements `Hash`. + +4. 🌟🌟 +```rust,editable + +// 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); + } +} +``` + +### Capacity +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). + +#### Example +```rust,editable + +use std::collections::HashMap; +fn main() { + let mut map: HashMap = 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!") +} +``` + +### Ownership +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. + +5. 🌟🌟 +```rust,editable +// 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"); +} +``` + +### Third-party Hash libs +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: +```rust +use std::hash::BuildHasherDefault; +use std::collections::HashMap; +// introduce a third party hash function +use twox_hash::XxHash64; + + +let mut hash: HashMap<_, _, BuildHasherDefault> = Default::default(); +hash.insert(42, "the answer"); +assert_eq!(hash.get(&42), Some(&"the answer")); +``` + diff --git a/zh-CN/src/collections/intro.md b/zh-CN/src/collections/intro.md index 1bdd607..ef29448 100644 --- a/zh-CN/src/collections/intro.md +++ b/zh-CN/src/collections/intro.md @@ -1 +1,4 @@ -# Collection Types +# 集合类型 +学习资源: +- 简体中文: [Rust语言圣经 - 集合类型](https://course.rs/basic/collections/intro.html) + diff --git a/zh-CN/src/collections/vector.md b/zh-CN/src/collections/vector.md index a2303c1..aed6588 100644 --- a/zh-CN/src/collections/vector.md +++ b/zh-CN/src/collections/vector.md @@ -1 +1,244 @@ # Vector +Vectors are re-sizable arrays. Like slices, their size is not known at compile time, but they can grow or shrink at any time. + +### Basic Operations +1. 🌟🌟🌟 +```rust,editable + +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 + // 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) {} +``` + + + +2. 🌟🌟 a Vec can be extended with `extend` method +```rust,editable + +// 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!") +} +``` + +### Turn X Into Vec +3. 🌟🌟🌟 +```rust,editable + +// 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 = arr.__(); + + assert_eq!(v1, v2); + + + // String -> Vec + // impl From for Vec + let s = "hello".to_string(); + let v1: Vec = 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 = [0; 10].into_iter().collect(); + assert_eq!(v4, vec![0; 10]); + + println!("Success!") + } +``` + +### Indexing +4. 🌟🌟🌟 +```rust,editable + +// 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!") +} +``` + + +### Slicing +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`. + +5. 🌟🌟 +```rust,editable + +// 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); + + // slice are read only + // Note: slice and &Vec are different + let vec_ref: &mut Vec = &mut v; + (*vec_ref).push(4); + let slice3 = &mut v[0..3]; + slice3.push(4); + + assert_eq!(slice3, &[1, 2, 3, 4]); + + println!("Success!") +} +``` +### Capacity +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. + +6. 🌟🌟 +```rust,editable +// 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!") +} +``` + +### Store distinct types in Vector +The elements in a vector mush be the same type, for example , the code below will cause an error: +```rust +fn main() { + let v = vec![1, 2.0, 3]; +} +``` + +But we can use enums or trait objects to store distinct types. + +7. 🌟🌟 +```rust,editable +#[derive(Debug)] +enum IpAddr { + V4(String), + V6(String), +} +fn main() { + // FILL in the blank + let v : Vec= __; + + // 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!") +} +``` + +8. 🌟🌟 +```rust,editable +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(); + } +} +``` \ No newline at end of file diff --git a/zh-CN/src/generics-traits/advance-traits.md b/zh-CN/src/generics-traits/advance-traits.md deleted file mode 100644 index 8cd2f63..0000000 --- a/zh-CN/src/generics-traits/advance-traits.md +++ /dev/null @@ -1 +0,0 @@ -# Advance Traits diff --git a/zh-CN/src/generics-traits/advanced-traits.md b/zh-CN/src/generics-traits/advanced-traits.md index 0cb2362..1fcccbe 100644 --- a/zh-CN/src/generics-traits/advanced-traits.md +++ b/zh-CN/src/generics-traits/advanced-traits.md @@ -1 +1,279 @@ -# 进一步深入特征 +# Advance Traits + +## Associated types +The use of "Associated types" improves the overall readability of code by moving inner types locally into a trait as output types. For example : +```rust +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 clearable and convenient than `AsRef<[u8]> + Clone + fmt::Debug + Eq + Hash`. + +1. 🌟🌟🌟 +```rust,editable + +struct Container(i32, i32); + +// USING associated types to re-implement trait Contains. +// trait Container { +// type A; +// type B; + +trait Contains { + fn contains(&self, _: &A, _: &B) -> bool; + fn first(&self) -> i32; + fn last(&self) -> i32; +} + +impl Contains 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>(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)); +} +``` + +## Default Generic Type Parameters +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. + +2. 🌟🌟 +```rust,editable + +use std::ops::Sub; + +#[derive(Debug, PartialEq)] +struct Point { + 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!") +} +``` + +## Fully Qualified Syntax +othing 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. + +#### Example +```rust,editable +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 `
::get` + assert_eq!(28, age); + + println!("Success!") +} +``` + +#### Exercise +3. 🌟🌟 +```rust,editable +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!") +} +``` + +## Supertraits +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. + +4. 🌟🌟🌟 +```rust,editable + +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(__)); +} +``` + +## Orphan Rules +We can’t implement external traits on external types. For example, we can’t implement the `Display` trait on `Vec` within our own crate, because `Display` and `Vec` are defined in the standard library and aren’t local to our crate. + +This restriction is often called as 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. + +5. 🌟🌟 +```rust +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](https://github.com/sunface/rust-by-practice)(under the solutions path), but only use it when you need it :) \ No newline at end of file diff --git a/zh-CN/src/generics-traits/trait-object.md b/zh-CN/src/generics-traits/trait-object.md index 38a0b34..411b49a 100644 --- a/zh-CN/src/generics-traits/trait-object.md +++ b/zh-CN/src/generics-traits/trait-object.md @@ -1 +1,229 @@ # Trait Object +In [traits chapter](https://practice.rs/generics-traits/traits.html#returning-types-that-implement-traits) we have seen that we can't use `impl Trait` when returning multiple types. + +Also one limitation of arrays is that they can only store elements of one type, yeah, enum is a not bad solution when our items are a fixed set of types in compile time, but trait object are more flexible and powerful here. + +## Returning Traits with dyn +The Rust compiler needs to know how much space a function's return type requires. Because the different implementations of a trait probably will need different amounts of memoery, this means function need to return a concrete type or the same type when using `impl Trait`, or it can return a trait object with `dyn`. + +1. 🌟🌟🌟 +```rust,editable + +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... + +``` +## Array with trait objects +2. 🌟🌟 +```rust,editable +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 turns into Bird, they all forgot how to fly, only remeber how to quack + // so, the below code will cause an error + // bird.fly(); + } +} +``` + + +## `&dyn` and `Box` + +3. 🌟🌟 +```rust,editable + +// 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) { + x.draw(); +} + +fn draw_with_ref(x: __) { + x.draw(); +} +``` + +## Static and Dynamic dispatch +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 did get extra flexibility when using dynamic dispatch. + +4. 🌟🌟 +```rust,editable + +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!") +} +``` + +## Object safe +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: + +- The return type isn’t `Self`. +- There are no generic type parameters. + +5. 🌟🌟🌟🌟 +```rust,editable + +// 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) { + 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](https://github.com/sunface/rust-by-practice)(under the solutions path), but only use it when you need it :) \ No newline at end of file diff --git a/zh-CN/src/generics-traits/traits.md b/zh-CN/src/generics-traits/traits.md index 445d444..6ac826e 100644 --- a/zh-CN/src/generics-traits/traits.md +++ b/zh-CN/src/generics-traits/traits.md @@ -1 +1,471 @@ # Traits +特征 Trait 可以告诉编译器一个特定的类型所具有的、且能跟其它类型共享的特性。我们可以使用特征通过抽象的方式来定义这种共享行为,还可以使用特征约束来限定一个泛型类型必须要具有某个特定的行为。 + +> Note: 特征跟其它语言的接口较为类似,但是仍然有一些区别 + +## 示例 +```rust,editable + +struct Sheep { naked: bool, name: String } + +impl Sheep { + fn is_naked(&self) -> bool { + self.naked + } + + fn shear(&mut self) { + if self.is_naked() { + // `Sheep` 结构体上定义的方法可以调用 `Sheep` 所实现的特征的方法 + println!("{} is already naked...", self.name()); + } else { + println!("{} gets a haircut!", self.name); + + self.naked = true; + } + } +} + + +trait Animal { + // 关联函数签名;`Self` 指代实现者的类型 + // 例如我们在为 Pig 类型实现特征时,那 `new` 函数就会返回一个 `Pig` 类型的实例,这里的 `Self` 指代的就是 `Pig` 类型 + fn new(name: String) -> Self; + + // 方法签名 + fn name(&self) -> String; + + fn noise(&self) -> String; + + // 方法还能提供默认的定义实现 + fn talk(&self) { + println!("{} says {}", self.name(), self.noise()); + } +} + +impl Animal for Sheep { + // `Self` 被替换成具体的实现者类型: `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() + } + } + + // 默认的特征方法可以被重写 + fn talk(&self) { + println!("{} pauses briefly... {}", self.name, self.noise()); + } +} + +fn main() { + // 这里的类型注释时必须的 + let mut dolly: Sheep = Animal::new("Dolly".to_string()); + // TODO ^ 尝试去除类型注释,看看会发生什么 + + dolly.talk(); + dolly.shear(); + dolly.talk(); +} +``` + +## Exercises +1. 🌟🌟 +```rust,editable + +// 完成两个 `impl` 语句块 +// 不要修改 `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!") +} +``` + +### Derive 派生 +我们可以使用 `#[derive]` 属性来派生一些特征,对于这些特征编译器会自动进行默认实现,对于日常代码开发而言,这是非常方便的,例如大家经常用到的 `Debug` 特征,就是直接通过派生来获取默认实现,而无需我们手动去完成这个工作。 + +想要查看更多信息,可以访问[这里](https://course.rs/appendix/derive.html)。 + +2. 🌟🌟 +```rust,editable + +// `Centimeters`, 一个元组结构体,可以被比较大小 +#[derive(PartialEq, PartialOrd)] +struct Centimeters(f64); + +// `Inches`, 一个元组结构体可以被打印 +#[derive(Debug)] +struct Inches(i32); + +impl Inches { + fn to_centimeters(&self) -> Centimeters { + let &Inches(inches) = self; + + Centimeters(inches as f64 * 2.54) + } +} + +// 添加一些属性让代码工作 +// 不要修改其它代码! +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_true = (_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); +} +``` + + +### 运算符 +在 Rust 中,许多运算符都可以被重载,事实上,运算符仅仅是特征方法调用的语法糖。例如 `a + b` 中的 `+` 是 `std::ops::Add` 特征的 `add` 方法调用,因此我们可以为自定义类型实现该特征来支持该类型的加法运算。 + +3. 🌟🌟 +```rust,editable + +use std::ops; + +// 实现 fn multiply 方法 +// 如上所述,`+` 需要 `T` 类型实现 `std::ops::Add` 特征 +// 那么, `*` 运算符需要实现什么特征呢? 你可以在这里找到答案: 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!") +} +``` + +4. 🌟🌟🌟 +```rust,editable + +// 修复错误,不要修改 `main` 中的代码!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` - the trait for addition with a RHS of type `Bar`. +// The following block implements the operation: Foo + Bar = FooBar +impl ops::Add for Foo { + type Output = FooBar; + + fn add(self, _rhs: Bar) -> FooBar { + FooBar + } +} + +impl ops::Sub for Bar { + type Output = BarFoo; + + fn sub(self, _rhs: Foo) -> BarFoo { + BarFoo + } +} + +fn main() { + // DON'T modify the below code + // you need to derive some trait for FooBar to make it comparable + assert_eq!(Foo + Bar, FooBar); + assert_eq!(Foo - Bar, BarFoo); + + println!("Success!") +} +``` + +### Use trait as function parameters +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. + +5. 🌟🌟🌟 +```rust,editable + +// 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 + +``` + +### Returning Types that Implement Traits +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, using Trait Objects instead when you really need to return serveral types. + +6. 🌟🌟 +```rust,editable + +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 erros 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()); +} +``` + +### Trait bound +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. + +7. 🌟🌟 +```rust, editable +fn main() { + assert_eq!(sum(1, 2), 3); +} + +// implement `fn sum` with trait bound in two ways +fn sum>(x: T, y: T) -> T { + x + y +} +``` +8. 🌟🌟 +```rust,editable +struct Pair { + x: T, + y: T, +} + +impl Pair { + fn new(x: T, y: T) -> Self { + Self { + x, + y, + } + } +} + +impl Pair { + 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(); +} +``` + +9. 🌟🌟🌟 +```rust,editable + +// 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 u32> { + calculation: T, + value: Option, + } + + impl u32> Cacher { + fn new(calculation: T) -> Cacher { + 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 constrain `T` + struct Cacher + where T: Fn(u32) -> u32, + { + calculation: T, + value: Option, + } + + impl Cacher + where T: Fn(u32) -> u32, + { + fn new(calculation: T) -> Cacher { + 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](https://github.com/sunface/rust-by-practice)(under the solutions path), but only use it when you need it :) \ No newline at end of file