add traits.md

This commit is contained in:
sunface 2022-03-05 01:13:40 +08:00
parent 5463a95d4e
commit 0d27589611
5 changed files with 802 additions and 6 deletions

View File

@ -6,10 +6,15 @@ This book was designed for easily diving into Rustand it's very easy to use:
## Features
- Read, Edit and Run the exercise ONLINE
- There are three parts in each chapter: examples, exercises and practices
- Covering nearly all aspects of Rust, such as **async/await, threads, sync primitives, optimizing and stand libraries** etc
- Solution for each exercise
- Difficulty from easy to super hard: easy 🌟 medium 🌟🌟 hard 🌟🌟🌟 super hard 🌟🌟🌟🌟
- Both [English](https://practice.rs) and [Chinsese](https://zh.practice.rs) are supported

View File

@ -1,7 +1,417 @@
1.
```rust
trait Hello {
fn say_hi(&self) -> String {
String::from("hi")
}
fn say_something(&self) -> String;
}
struct Student {}
impl Hello for Student {
fn say_something(&self) -> String {
String::from("I'm a good student")
}
}
struct Teacher {}
impl Hello for Teacher {
fn say_hi(&self) -> String {
String::from("Hi, I'm your new teacher")
}
fn say_something(&self) -> String {
String::from("I'm not a bad 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");
}
```
1.
2.
```rust
// `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
#[derive(Debug,PartialEq,PartialOrd)]
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);
}
```
3.
```rust
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 multiply<T: ops::Mul<Output = T>>(x: T, y: T) -> T {
x * y
}
fn main() {
assert_eq!(6, multiply(2u8, 3u8));
assert_eq!(5.0, multiply(1.0, 5.0));
}
```
4.
```rust
use std::ops;
struct Foo;
struct Bar;
#[derive(PartialEq, Debug)]
struct FooBar;
#[derive(PartialEq, Debug)]
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<Bar> for Foo {
type Output = BarFoo;
fn sub(self, _rhs: Bar) -> 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);
}
```
5.
```rust
// 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);
}
fn summary(t: &impl Summary) {
let _ = t.summarize();
}
```
6.
```rust
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 {
Sheep {}
}
}
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());
}
```
```rust
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) -> Box<dyn Animal> {
if random_number < 0.5 {
Box::new(Sheep {})
} else {
Box::new(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());
}
```
7.
```rust
fn main() {
assert_eq!(sum(1, 2), 3);
assert_eq!(sum(1.0, 2.0), 3.0);
}
fn sum<T: std::ops::Add<Output = T>>(x: T, y: T) -> T {
x + y
}
```
```rust
fn main() {
assert_eq!(sum(1, 2), 3);
assert_eq!(sum(1.0, 2.0), 3.0);
}
fn sum<T>(x: T, y: T) -> T
where
T: std::ops::Add<Output = T>,
{
x + y
}
```
8.
```rust
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);
}
}
}
#[derive(Debug, PartialEq, PartialOrd)]
struct Unit(i32);
fn main() {
let pair = Pair{
x: Unit(1),
y: Unit(3)
};
pair.cmp_display();
}
```
9.
```rust
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), 11);
assert_eq!(cacher.value(15), 11);
}
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), 21);
assert_eq!(cacher.value(25), 21);
}
fn main() {
example1();
example2();
}
```

View File

@ -1,10 +1,11 @@
# 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 a feature often called interfaces in other languages, although with some differences.
> Note: Traits are similar to interfaces in other languages, although with some differences.
## Examples
```rust,editable
struct Sheep { naked: bool, name: String }
trait Animal {
@ -76,8 +77,384 @@ fn main() {
}
```
## 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() {
assert_eq!(5, sum(2i8, 3u8));
assert_eq!(50, sum(20, 30.1));
}
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");
}
```
### 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));
}
```
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);
}
```
### 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 youre 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: std::ops::Add<Output = T>>(x: T, y: T) -> T {
x + y
}
```
8. 🌟🌟
```rust,editable
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();
}
```

View File

@ -4,9 +4,13 @@ This book was designed for easily diving into Rustand it's very easy to use:
## Features
- There are three parts in each chapter: examples, exercises and practices
- Covering nearly all aspects of Rust, such as **async/await, threads, sync primitives, optimizing and stand libraries** etc
- Solution for each exercise
- Difficulty from easy to super hard: easy 🌟 medium 🌟🌟 hard 🌟🌟🌟 super hard 🌟🌟🌟🌟
- Both [English](https://practice.rs) and [Chinsese](https://zh.practice.rs) are supported
> Part of our examples and exercises are borrowed from [Rust By Example](https://github.com/rust-lang/rust-by-example), thanks for the great works you have been doing!

View File

@ -14,7 +14,7 @@
2. 覆盖面更广,除了 Rust 语言本身外,还包括了**async异步编程, 多线程, 并发原语, 性能优化和标准库等内容s**
3. 通过一些小型时间项目来实践 Rust
3. 通过一些小型实践项目来实践 Rust
4. 支持英语和中文