You've already forked comprehensive-rust
mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-06-22 16:57:41 +02:00
@ -1,38 +0,0 @@
|
||||
# Closures
|
||||
|
||||
Closures or lambda expressions have types which cannot be named. However, they
|
||||
implement special [`Fn`](https://doc.rust-lang.org/std/ops/trait.Fn.html),
|
||||
[`FnMut`](https://doc.rust-lang.org/std/ops/trait.FnMut.html), and
|
||||
[`FnOnce`](https://doc.rust-lang.org/std/ops/trait.FnOnce.html) traits:
|
||||
|
||||
```rust,editable
|
||||
fn apply_with_log(func: impl FnOnce(i32) -> i32, input: i32) -> i32 {
|
||||
println!("Calling function on {input}");
|
||||
func(input)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let add_3 = |x| x + 3;
|
||||
let mul_5 = |x| x * 5;
|
||||
|
||||
println!("add_3: {}", apply_with_log(add_3, 10));
|
||||
println!("mul_5: {}", apply_with_log(mul_5, 20));
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
If you have an `FnOnce`, you may only call it once. It might consume captured values.
|
||||
|
||||
An `FnMut` might mutate captured values, so you can call it multiple times but not concurrently.
|
||||
|
||||
An `Fn` neither consumes nor mutates captured values, or perhaps captures nothing at all, so it can
|
||||
be called multiple times concurrently.
|
||||
|
||||
`FnMut` is a subtype of `FnOnce`. `Fn` is a subtype of `FnMut` and `FnOnce`. I.e. you can use an
|
||||
`FnMut` wherever an `FnOnce` is called for, and you can use an `Fn` wherever an `FnMut` or `FnOnce`
|
||||
is called for.
|
||||
|
||||
`move` closures only implement `FnOnce`.
|
||||
|
||||
</details>
|
@ -15,3 +15,11 @@ fn main() {
|
||||
println!("{integer:?} and {float:?}");
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* Try declaring a new variable `let p = Point { x: 5, y: 10.0 };`.
|
||||
|
||||
* Fix the code to allow points that have elements of different types.
|
||||
|
||||
</details>
|
@ -1,44 +0,0 @@
|
||||
# `impl Trait`
|
||||
|
||||
Similar to trait bounds, an `impl Trait` syntax can be used in function
|
||||
arguments and return values:
|
||||
|
||||
```rust,editable
|
||||
use std::fmt::Display;
|
||||
|
||||
fn get_x(name: impl Display) -> impl Display {
|
||||
format!("Hello {name}")
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let x = get_x("foo");
|
||||
println!("{x}");
|
||||
}
|
||||
```
|
||||
|
||||
* `impl Trait` allows you to work with types which you cannot name.
|
||||
|
||||
<details>
|
||||
|
||||
The meaning of `impl Trait` is a bit different in the different positions.
|
||||
|
||||
* For a parameter, `impl Trait` is like an anonymous generic parameter with a trait bound.
|
||||
|
||||
* For a return type, it means that the return type is some concrete type that implements the trait,
|
||||
without naming the type. This can be useful when you don't want to expose the concrete type in a
|
||||
public API.
|
||||
|
||||
Inference is hard in return position. A function returning `impl Foo` picks
|
||||
the concrete type it returns, without writing it out in the source. A function
|
||||
returning a generic type like `collect<B>() -> B` can return any type
|
||||
satisfying `B`, and the caller may need to choose one, such as with `let x:
|
||||
Vec<_> = foo.collect()` or with the turbofish, `foo.collect::<Vec<_>>()`.
|
||||
|
||||
This example is great, because it uses `impl Display` twice. It helps to explain that
|
||||
nothing here enforces that it is _the same_ `impl Display` type. If we used a single
|
||||
`T: Display`, it would enforce the constraint that input `T` and return `T` type are the same type.
|
||||
It would not work for this particular function, as the type we expect as input is likely not
|
||||
what `format!` returns. If we wanted to do the same via `: Display` syntax, we'd need two
|
||||
independent generic parameters.
|
||||
|
||||
</details>
|
@ -1,50 +0,0 @@
|
||||
# Trait Bounds
|
||||
|
||||
When working with generics, you often want to require the types to implement
|
||||
some trait, so that you can call this trait's methods.
|
||||
|
||||
You can do this with `T: Trait` or `impl Trait`:
|
||||
|
||||
```rust,editable
|
||||
fn duplicate<T: Clone>(a: T) -> (T, T) {
|
||||
(a.clone(), a.clone())
|
||||
}
|
||||
|
||||
// Syntactic sugar for:
|
||||
// fn add_42_millions<T: Into<i32>>(x: T) -> i32 {
|
||||
fn add_42_millions(x: impl Into<i32>) -> i32 {
|
||||
x.into() + 42_000_000
|
||||
}
|
||||
|
||||
// struct NotClonable;
|
||||
|
||||
fn main() {
|
||||
let foo = String::from("foo");
|
||||
let pair = duplicate(foo);
|
||||
println!("{pair:?}");
|
||||
|
||||
let many = add_42_millions(42_i8);
|
||||
println!("{many}");
|
||||
let many_more = add_42_millions(10_000_000);
|
||||
println!("{many_more}");
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
Show a `where` clause, students will encounter it when reading code.
|
||||
|
||||
```rust,ignore
|
||||
fn duplicate<T>(a: T) -> (T, T)
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
(a.clone(), a.clone())
|
||||
}
|
||||
```
|
||||
|
||||
* It declutters the function signature if you have many parameters.
|
||||
* It has additional features making it more powerful.
|
||||
* If someone asks, the extra feature is that the type on the left of ":" can be arbitrary, like `Option<T>`.
|
||||
|
||||
</details>
|
@ -1,102 +0,0 @@
|
||||
# Trait Objects
|
||||
|
||||
We've seen how a function can take arguments which implement a trait:
|
||||
|
||||
```rust,editable
|
||||
use std::fmt::Display;
|
||||
|
||||
fn print<T: Display>(x: T) {
|
||||
println!("Your value: {x}");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
print(123);
|
||||
print("Hello");
|
||||
}
|
||||
```
|
||||
|
||||
However, how can we store a collection of mixed types which implement `Display`?
|
||||
|
||||
```rust,editable,compile_fail
|
||||
fn main() {
|
||||
let displayables = vec![123, "Hello"];
|
||||
}
|
||||
```
|
||||
|
||||
For this, we need _trait objects_:
|
||||
|
||||
```rust,editable
|
||||
use std::fmt::Display;
|
||||
|
||||
fn main() {
|
||||
let displayables: Vec<Box<dyn Display>> = vec![Box::new(123), Box::new("Hello")];
|
||||
for x in displayables {
|
||||
println!("x: {x}");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Memory layout after allocating `displayables`:
|
||||
|
||||
```bob
|
||||
Stack Heap
|
||||
.- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - - - - - - -.
|
||||
: : : :
|
||||
: displayables : : :
|
||||
: +-----------+-------+ : : +-----+-----+ :
|
||||
: | ptr | o---+---+-----+-->| o o | o o | :
|
||||
: | len | 2 | : : +-|-|-+-|-|-+ :
|
||||
: | capacity | 2 | : : | | | | +----+----+----+----+----+ :
|
||||
: +-----------+-------+ : : | | | '-->| H | e | l | l | o | :
|
||||
: : : | | | +----+----+----+----+----+ :
|
||||
`- - - - - - - - - - - - - -' : | | | :
|
||||
: | | | +-------------------------+ :
|
||||
: | | '---->| "<str as Display>::fmt" | :
|
||||
: | | +-------------------------+ :
|
||||
: | | :
|
||||
: | | +----+----+----+----+ :
|
||||
: | '-->| 7b | 00 | 00 | 00 | :
|
||||
: | +----+----+----+----+ :
|
||||
: | :
|
||||
: | +-------------------------+ :
|
||||
: '---->| "<i32 as Display>::fmt" | :
|
||||
: +-------------------------+ :
|
||||
: :
|
||||
: :
|
||||
'- - - - - - - - - - - - - - - - - - - - - - - -'
|
||||
```
|
||||
|
||||
Similarly, you need a trait object if you want to return different types
|
||||
implementing a trait:
|
||||
|
||||
```rust,editable
|
||||
fn numbers(n: i32) -> Box<dyn Iterator<Item=i32>> {
|
||||
if n > 0 {
|
||||
Box::new(0..n)
|
||||
} else {
|
||||
Box::new((n..0).rev())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("{:?}", numbers(-5).collect::<Vec<_>>());
|
||||
println!("{:?}", numbers(5).collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
<details>
|
||||
|
||||
* Types that implement a given trait may be of different sizes. This makes it impossible to have things like `Vec<Display>` in the example above.
|
||||
* `dyn Display` is a way to tell the compiler about a dynamically sized type that implements `Display`.
|
||||
* In the example, `displayables` holds *fat pointers* to objects that implement `Display`. The fat pointer consists of two components, a pointer to the actual object and a pointer to the virtual method table for the `Display` implementation of that particular object.
|
||||
* Compare these outputs in the above example:
|
||||
```rust,ignore
|
||||
use std::fmt::Display;
|
||||
println!("{}", std::mem::size_of::<u32>());
|
||||
println!("{}", std::mem::size_of::<&u32>());
|
||||
println!("{}", std::mem::size_of::<&dyn Display>());
|
||||
println!("{}", std::mem::size_of::<Box<dyn Display>>());
|
||||
```
|
||||
|
||||
</details>
|
Reference in New Issue
Block a user