Skip to content

fixed links, typos, format in md-files #109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions 0_vocabulary/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ Polish your familiarity by completing [Rust By Example] and [rustlings].
Read through [Cargo Book] and become familiar with [Cargo] and its workspaces.

After completing these steps, you should be able to answer (and understand why) the following questions:

- What [memory model][31] [Rust] has? Is it single-threaded or multi-threaded? Is it synchronous or asynchronous? What are the memory layouts of `Box` and `Vector`? What are a heap and a stack? Where, but on heap and stack data could live in RAM?
- What runtime [Rust] has? Does it use a GC (garbage collector)?
- What is special about slice? What is the layout of Rust standard data types? Difference between fat and thin pointers?
- Why does [Rust] have `&str` and `String` types? How do they differ? When should you use them? Why str slice coexists with slice? What is the difference between `String` and `Vec`?
- Why does [Rust] have `&str` and `String` types? How do they differ? When should you use them? Why `str` slice coexists with slice? What is the difference between `String` and `Vec`?
- What static typing means? What are the benefits of using it? Weak vs strong typing? Implicit vs explicit typing?
- What are generics and parametric polymorphism? Which problems do they solve?
- What are nominative typing and structural typing? What is the difference?
Expand All @@ -35,6 +36,7 @@ After completing these steps, you should be able to answer (and understand why)
After you are done, notify your lead in an appropriate PR (pull request), and they will examine what you have learned.

_Additional_ articles, which may help to understand the above topic better:

- [Chris Morgan: Rust ownership, the hard way][1]
- [Adolfo Ochagavía: You are holding it wrong][23]
- [Vikram Fugro: Beyond Pointers: How Rust outshines C++ with its Borrow Checker][30]
Expand All @@ -59,8 +61,8 @@ _Additional_ articles, which may help to understand the above topic better:
- [Georgios Antonopoulos: Rust vs Common C++ Bugs][21]
- [Yurii Shymon: True Observer Pattern with Unsubscribe mechanism using Rust][22]


Additional:

- [Rust API guidline checklist][19]
- [Interview Questions on Rust Programming][20]
- [Step-by-step instruction to start development in Rust][26]
Expand Down Expand Up @@ -104,4 +106,3 @@ Additional:
[28]: https://github.com/rust-lang-ua/learn_rust_together/blob/master/learn.md
[30]: https://dev.to/vikram2784/beyond-pointers-how-rust-outshines-c-with-its-borrow-checker-1mad
[31]: https://en.wikipedia.org/wiki/Memory_model_(programming)

26 changes: 6 additions & 20 deletions 1_concepts/1_1_default_clone_copy/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
Task 1.1: Default values, cloning and copying
=============================================




## Default values

[Rust] has a standard way to deal with default values of a type via [`Default`] trait. Read through [its official docs][`Default`] to understand the design.

It can be auto-derived, but only for a `struct` whose all members have [`Default`] implementations. It is implemented for a great many types in the standard library, and also used in a surprising number of places. So if your type has a value that can be construed as being "default", it is a good idea to implement this trait.

If you're not satisfied with [std] deriving capabilities for [`Default`], consider using [smart-default] crate. An example is quite self-explanatory:

```rust
#[derive(SmartDefault)]
enum Foo {
Expand All @@ -32,13 +30,11 @@ enum Foo {
```

A great thing that having a [`Default`] implementation you can instantiate your `struct` with only the non-default values and have all other fields filled with default values:

```rust
let x = Foo { bar: baz, ..Default::default() };
```




## Cloning and copying

By default, all types in [Rust] follow ['move semantics'][1].
Expand All @@ -52,43 +48,33 @@ However, [`Copy`] marker trait (see [official docs][`Copy`]) enables 'copy seman
> Some types can't be copied safely. For example, copying `&mut T` would create an aliased mutable reference. Copying `String` would duplicate responsibility for managing the `String`'s buffer, leading to a double free.
>
> Generalizing the latter case, any type implementing `Drop` can't be `Copy`, because it's managing some resource besides its own `size_of::<T>` bytes.

>
> Generally speaking, if your type can implement `Copy`, it should. Keep in mind, though, that implementing `Copy` is part of the public API of your type. If the type might become non-`Copy` in the future, it could be prudent to omit the `Copy` implementation now, to avoid a breaking API change.

For better understanding the topic, read through:

- [Official `Clone` docs][`Clone`]
- [Official `Copy` docs][`Copy`]
- [HashRust: Moves, copies and clones in Rust][2]




## Task

__Estimated time__: 1 day




- Create a `Point` type which represents a 2D point (`x` and `y` coordinates). This type has to be `Copy` and `Default`.
- Create a `Polyline` type which represents a non-empty collection(whichever you want) of `Point`s of unknown size. This type has to be `Clone` and non-`Default`.
- Polyline must implement basic collection methods: addition of an item, removal of an item,
- Polyline must implement basic collection methods: addition of an item, removal of an item,
getting an item. You may add additional methods & features if you desire.




## Questions

After completing everything above, you should be able to answer (and understand why) the following questions:

- What purpose does [`Default`] trait serve in [Rust]?
- What is `#[derive(Default)]` from `std` capable of? What does it wrong? Which are alternatives?
- What does [`Clone`] mean semantically?
- What does [`Copy`] mean semantically? How is it connected with [`Clone`]? Which limitations does it have and why?




[`Clone`]: https://doc.rust-lang.org/std/clone/trait.Clone.html
[`Copy`]: https://doc.rust-lang.org/std/marker/trait.Copy.html
[`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html
Expand Down
32 changes: 11 additions & 21 deletions 1_concepts/1_2_box_pin/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
Task 1.2: Boxing and pinning
============================




## Boxing

[`Box`] is a pointer that owns heap-allocated data. This is the most common and simples form of [heap] allocation in [Rust].
Expand All @@ -13,14 +10,12 @@ It's more idiomatic to use references (`&T`/`&mut T`) for pointing to the data,
[`Box`] is also a way to go if an owned [slice] is needed, but is not intended to be resized. For example, `Box<str>`/`Box<[T]>` are often used instead `String`/`Vec<T>` in such cases.

For better understanding [`Box`] purpose, design, limitations and use cases read through:

- [Rust Book: 15.1. Using Box to Point to Data on the Heap][1]
- [Official `std::boxed` docs][`std::boxed`]
- [Amos: What's in the box?][3]
- [Mahdi Dibaiee: What is `Box<str>` and how is it different from `String` in Rust?][8]




## Pinning

It is sometimes useful to have objects that are guaranteed to not move, in the sense that their placement in memory does not change, and can thus be relied upon. A prime example of such a scenario would be building self-referential structs, since moving an object with pointers to itself will invalidate them, which could cause undefined behavior.
Expand All @@ -32,6 +27,7 @@ However, many types are always freely movable, even when pinned, because they do
Note, that pinning and [`Unpin`] only affect the pointed-to type `P::Target`, not the pointer type `P` itself that got wrapped in `Pin<P>`. For example, whether or not `Box<T>` is `Unpin` has no effect on the behavior of `Pin<Box<T>>` (here, `T` is the pointed-to type).

For better understanding [`Pin`] purpose, design, limitations and use cases read through:

- [Official `std::pin` docs][`std::pin`]
- [Reddit: Pinned objects ELI5?][2]
- [SoByte: Pin and Unpin in Rust][10]
Expand All @@ -44,25 +40,22 @@ For better understanding [`Pin`] purpose, design, limitations and use cases read
- [Ohad Ravid: Put a Pin on That][12]
- [Razieh Behjati: Leaky Abstractions and a Rusty Pin][13]




## Task

__Estimated time__: 1 day

Implement the following traits:

1. Implement the `SayHi` and `MutMeSomehow` traits __for the following types__: `Box<T>`, `Rc<T>`, `Vec<T>`, `String`, `&[u8]`, `T`.

#### Important

Implement the following traits:
1. Implement the `SayHi` and `MutMeSomehow` traits **for the following types**: `Box<T>`, `Rc<T>`, `Vec<T>`, `String`, `&[u8]`, `T`.

#### Important:
##### THERE HAS TO BE NO `unsafe` CODE (DON'T USE `unsafe`)

> - `mut_me_somehow` must mutate self somehow.
> - You can add trait bounds to the types. (using `Unpin` trait bound is not allowed)
> - Write simple tests to demonstrate mut_me_somehow.
> - you may use modules to avoid conflicting implementations
> - You may use modules to avoid conflicting implementations

```rust
trait SayHi: fmt::Debug {
Expand All @@ -71,6 +64,7 @@ __Estimated time__: 1 day
}
}
```

```rust
trait MutMeSomehow {
fn mut_me_somehow(self: Pin<&mut Self>) {
Expand All @@ -84,32 +78,28 @@ __Estimated time__: 1 day
}
```

2. For the following structure:

3. For the following structure:
```rust
struct MeasurableFuture<Fut> {
inner_future: Fut,
started_at: Option<std::time::Instant>,
}
```
Provide a [`Future`] trait implementation, transparently polling the `inner_future`, and printing its execution time in nanoseconds once it's ready. Using `Fut: Unpin` trait bound (or similar) is not allowed.



Provide a [`Future`] trait implementation, transparently polling the `inner_future`, and printing its execution time in nanoseconds once it's ready. Using `Fut: Unpin` trait bound (or similar) is not allowed.

## Questions

After completing everything above, you should be able to answer (and understand why) the following questions:

- What does "boxing" mean in [Rust]? How is it useful? When and why is it required?
- What is [`Pin`] and why is it required? What guarantees does it provide? How does it fulfill them?
- How does [`Unpin`] affect the [`Pin`]? What does it mean?
- Is it allowed to move pinned data after the [`Pin`] dies? Why?
- What is structural pinning? When it should be used and why?
- What is [`Pin`] projection? Why does it exist? How is it used?




[`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html
[`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html
[`Pin`]: https://doc.rust-lang.org/std/pin/struct.Pin.html
Expand Down
35 changes: 12 additions & 23 deletions 1_concepts/1_3_rc_cell/README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
Task 1.3: Shared ownership and interior mutability
==================================================




## Shared ownership

[Rust] ownership model allows _only one owner of a value_. However, there are situations when multiple ownership is required, and it's important to understand how this can be accomplished.

The key piece is to put a value behind a smart pointer, so the pointer itself can be __cloned many times__ (thus allowing multiple owners), but is __pointing always to the same value__ (thus sharing a value). In [Rust] there is a [`Rc`] (["reference counted"][`std::rc`]) smart pointer for this purpose, and [`Arc`] ("atomic reference counted") for use in multiple threads. Both automatically destroy a value once there are no references left.

The code below won't compile as `a` is owned by `x` and moved to a heap before is passed to `y`:

```rust
struct Val(u8);

let a = Val(5);
let x = Box::new(a);
let y = Box::new(a);
```

```rust
error[E0382]: use of moved value: `a`
--> src/main.rs:6:22
Expand All @@ -31,6 +30,7 @@ error[E0382]: use of moved value: `a`
```

However, [`Rc`] allows that:

```rust
let a = Rc::new(Val(5));
let x = Rc::clone(&a); // does not clone original value,
Expand All @@ -40,18 +40,17 @@ let y = Rc::clone(&a); // but rather produces new reference to it
The [`Rc`], however, __should be used wisely__ as __it won't deallocate memory on references cycle__, which is exactly what a __memory leak__ is. [Rust] is unable to prevent memory leaks at compile time, even though makes hard to produce them. If it's still required to have a references cycle, you should use a [`Weak`] smart pointer ("weak reference") in combination with [`Rc`]. [`Weak`] allows to break a references cycle as it can refer to a value that has already been dropped(returns `None` in such case).

For better understanding [`Rc`]/[`Weak`] purpose, design, limitations and use cases read through:

- [Rust Book: 15.4. Rc, the Reference Counted Smart Pointer][1]
- [Rust Book: 15.6. Reference Cycles Can Leak Memory][2]
- [Official `std::rc` docs][`std::rc`]




## Interior mutability

[Rust] memory safety is based on the following rules (known as "borrowing rules"):

> Given an object `T`, it is only possible to have one of the following:
>
> - Having several immutable references (`&T`) to the object (also known as __aliasing__).
> - Having one mutable reference (`&mut T`) to the object (also known as __mutability__).

Expand All @@ -60,34 +59,31 @@ However, quite often there are situations where these rules are not flexible eno
These containers __allow to overcome [Rust] borrowing rules and track borrows at runtime__ (so called "dynamic borrowing"), which, obviously, leads to less safe code as compile-time errors become runtime panics. That's why one should __use [`Cell`]/[`RefCell`] wisely and only as a last resort__.

For better understanding [`Cell`]/[`RefCell`] purpose, design, limitations and use cases read through:

- [Rust Book: 15.5. RefCell and the Interior Mutability Pattern][3]
- [Official `std::cell` docs][`std::cell`]
- [Paul Dicker: Interior mutability patterns][6]
- [David Tolnay: Accurate mental model for Rust’s reference types][8]




## Shared mutability

The most spread case is a combination of two previous: `Rc<RefCell<T>>` (or `Arc<Mutex<T>>`). This allows to mutate a value via multiple owners.

A real-world example would be a database client object: it _must be mutable_, as mutates its state under-the-hood (opens network connections, manages database sessions, etc.), yet _we need to own it in multiple places_ of our code, not a single one.

The following articles may explain you this concept better:

- [Manish Goregaokar: Wrapper Types in Rust: Choosing Your Guarantees][4]
- [Alexandre Beslic: Rust, Builder Pattern, Trait Objects, `Box<T>` and `Rc<T>`][5]




## Avoiding panics and deadlocks

There is a simple rule for omitting deadlocks with [`Mutex`]/[`RwLock`] (applicable for panics with [`Cell`]/[`RefCell`] types too):

> Locking scopes must not intersect in any way.

The following example explains why deadlocks happen:

```rust
let owner1 = Arc::new(Mutex::new("string"));
let owner2 = owner1.clone();
Expand All @@ -99,6 +95,7 @@ let value = owner2.lock.unwrap();
```

Let's remove the intersection:

```rust
let owner1 = Arc::new(Mutex::new("string"));
let owner2 = owner1.clone();
Expand Down Expand Up @@ -136,38 +133,30 @@ owner2.mutate_somehow();
```

And even when there is no possibility to hide lock guards behind API boundary, it may be feasible to try encoding the described property via type system, using zero-sized wrapper types on guards. See the following articles for examples and design insights:
- [Adrian Taylor: Can the Rust type system prevent deadlocks?][7]



- [Adrian Taylor: Can the Rust type system prevent deadlocks?][7]

## Task

__Estimated time__: 1 day




Write a `GlobalStack<T>` collection which represents a trivial unsized [stack] (may grow infinitely) and has the following semantics:

- can be mutated through multiple shared references (`&GlobalStack<T>`);
- cloning doesn't clone data, but only produces a pointer, so multiple owners mutate the same data.

Implement tests for `GlobalStack<T>`.



## Questions

After completing everything above, you should be able to answer (and understand why) the following questions:

- What is shared ownership? Which problem does it solve? Which penalties does it have?
- What is interior mutability? Why is it required in [Rust]? In what price does it come?
- Is it possible to write a custom type with interior mutability without using `std`? Why?
- What is shared mutability? Which are its common use-cases?
- How can we expose panic/deadlock-free API to users when using interior mutability?




[`Arc`]: https://doc.rust-lang.org/std/sync/struct.Arc.html
[`Cell`]: https://doc.rust-lang.org/std/cell/struct.Cell.html
[`Mutex`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html
Expand Down
Loading