mirror of
https://github.com/sunface/rust-by-practice.git
synced 2025-08-12 06:24:44 +00:00
update repo layout
This commit is contained in:
279
en/src/generics-traits/advanced-traits.md
Normal file
279
en/src/generics-traits/advanced-traits.md
Normal file
@@ -0,0 +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 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));
|
||||
}
|
||||
```
|
||||
|
||||
## 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<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!")
|
||||
}
|
||||
```
|
||||
|
||||
## Fully Qualified Syntax
|
||||
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.
|
||||
|
||||
#### 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 `<Form as AgeWidget>::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<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 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,editable
|
||||
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 :)
|
145
en/src/generics-traits/const-generics.md
Normal file
145
en/src/generics-traits/const-generics.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# Const Generics
|
||||
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.
|
||||
|
||||
## Examples
|
||||
1. Here's an example of a type and implementation making use of const generics: a type wrapping a pair of arrays of the same size.
|
||||
```rust,editable
|
||||
struct ArrayPair<T, const N: usize> {
|
||||
left: [T; N],
|
||||
right: [T; N],
|
||||
}
|
||||
|
||||
impl<T: Debug, const N: usize> Debug for ArrayPair<T, N> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
2. Currently, const parameters may only be instantiated by const arguments of the following forms:
|
||||
|
||||
- A standalone const parameter.
|
||||
- A literal (i.e. an integer, bool, or character).
|
||||
- A concrete constant expression (enclosed by {}), involving no generic parameters.
|
||||
|
||||
```rust,editable
|
||||
fn foo<const N: usize>() {}
|
||||
|
||||
fn bar<T, const M: usize>() {
|
||||
foo::<M>(); // ok: `M` is a const parameter
|
||||
foo::<2021>(); // ok: `2021` is a literal
|
||||
foo::<{20 * 100 + 20 * 10 + 1}>(); // ok: 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]; // ok: `M` is a const parameter
|
||||
let _: [u8; std::mem::size_of::<T>()]; // error: const expression contains the generic parameter `T`
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
```
|
||||
|
||||
3. Const generics can also let us avoid some runtime checks.
|
||||
```rust
|
||||
/// 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' ')
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Exercises
|
||||
1. 🌟🌟 `<T, const N: usize>` is part of the struct type, it means `Array<i32, 3>` and `Array<i32, 4>` are different types.
|
||||
|
||||
```rust,editable
|
||||
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!")
|
||||
}
|
||||
```
|
||||
|
||||
2. 🌟🌟
|
||||
```rust,editable
|
||||
|
||||
// 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);
|
||||
}
|
||||
```
|
||||
|
||||
3. 🌟🌟🌟 Sometimes we want to limit the size of an variable, e.g when using in embedding evironments, then `const expressions` will fit your need.
|
||||
|
||||
```rust,editable
|
||||
#![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(["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](https://github.com/sunface/rust-by-practice)(under the solutions path), but only use it when you need it :)
|
156
en/src/generics-traits/generics.md
Normal file
156
en/src/generics-traits/generics.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# Generics
|
||||
|
||||
### Functions
|
||||
1. 🌟🌟🌟
|
||||
```rust,editable
|
||||
|
||||
// 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!")
|
||||
}
|
||||
```
|
||||
|
||||
2. 🌟🌟 A function call with explicitly specified type parameters looks like: `fun::<A, B, ...>()`.
|
||||
```rust,editable
|
||||
|
||||
// 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!")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Struct and `impl`
|
||||
|
||||
3. 🌟
|
||||
```rust,editable
|
||||
|
||||
// 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!")
|
||||
}
|
||||
```
|
||||
|
||||
4. 🌟🌟
|
||||
```rust,editable
|
||||
|
||||
// modify this struct to make the code work
|
||||
struct Point<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// DON'T modify here
|
||||
let p = Point{x: 5, y : "hello".to_string()};
|
||||
|
||||
println!("Success!")
|
||||
}
|
||||
```
|
||||
|
||||
5. 🌟🌟
|
||||
```rust,editable
|
||||
|
||||
// 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());
|
||||
}
|
||||
```
|
||||
|
||||
### Method
|
||||
6. 🌟🌟🌟
|
||||
|
||||
```rust,editable
|
||||
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!")
|
||||
}
|
||||
```
|
||||
|
||||
7. 🌟🌟
|
||||
```rust,editable
|
||||
|
||||
// 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](https://github.com/sunface/rust-by-practice)(under the solutions path), but only use it when you need it
|
||||
|
6
en/src/generics-traits/intro.md
Normal file
6
en/src/generics-traits/intro.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Generics and Traits
|
||||
Learning resources:
|
||||
- English: [Rust Book 10.1, 10.2](https://doc.rust-lang.org/book/ch10-00-generics.html)
|
||||
- 简体中文: [Rust语言圣经 - 模式匹配](https://course.rs/basic/trait/intro.html)
|
||||
|
||||
|
229
en/src/generics-traits/trait-object.md
Normal file
229
en/src/generics-traits/trait-object.md
Normal file
@@ -0,0 +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<dyn>`
|
||||
|
||||
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<dyn Draw>) {
|
||||
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<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](https://github.com/sunface/rust-by-practice)(under the solutions path), but only use it when you need it :)
|
472
en/src/generics-traits/traits.md
Normal file
472
en/src/generics-traits/traits.md
Normal file
@@ -0,0 +1,472 @@
|
||||
# Traits
|
||||
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.
|
||||
|
||||
## Examples
|
||||
```rust,editable
|
||||
|
||||
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();
|
||||
}
|
||||
```
|
||||
|
||||
## Exercises
|
||||
1. 🌟🌟
|
||||
```rust,editable
|
||||
|
||||
// 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!")
|
||||
}
|
||||
```
|
||||
|
||||
### Derive
|
||||
The compiler is capable of providing basic implementations for some traits via
|
||||
the `#[derive]` attribute. For more info, please visit [here](https://doc.rust-lang.org/book/appendix-03-derivable-traits.html).
|
||||
|
||||
2. 🌟🌟
|
||||
```rust,editable
|
||||
|
||||
// `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 codes!
|
||||
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);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Operator
|
||||
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.
|
||||
|
||||
3. 🌟🌟
|
||||
```rust,editable
|
||||
|
||||
use std::ops;
|
||||
|
||||
// implement fn multiply to make the code work
|
||||
// As mentiond 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!")
|
||||
}
|
||||
```
|
||||
|
||||
4. 🌟🌟🌟
|
||||
```rust,editable
|
||||
|
||||
// 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 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<T>(x: T, y: T) -> T {
|
||||
x + y
|
||||
}
|
||||
```
|
||||
8. 🌟🌟
|
||||
```rust,editable
|
||||
|
||||
// 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();
|
||||
}
|
||||
```
|
||||
|
||||
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<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 constrain `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](https://github.com/sunface/rust-by-practice)(under the solutions path), but only use it when you need it :)
|
Reference in New Issue
Block a user