1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-06-22 16:57:41 +02:00

Restructure Day-3 morning (#503)

* Restructure Day-3 morning
This commit is contained in:
rbehjati
2023-03-30 13:25:34 +01:00
committed by GitHub
parent 780368b4f7
commit 739b3a01e0
10 changed files with 123 additions and 126 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>