mainmatter / 100-exercises-to-learn-rust Goto Github PK
View Code? Open in Web Editor NEWA self-paced course to learn Rust, one exercise at a time.
Home Page: https://rust-exercises.com
A self-paced course to learn Rust, one exercise at a time.
Home Page: https://rust-exercises.com
Hello @LukeMathWalker,
In this solution can't we maybe start the for
loop from number 2? In this way we can optimize, skip 1 loop and avoid multiplying the result
by 1:
I just finished 03_ticket_v1
and was wondering why you would want to return a reference to an integral type? Because a reference is usize
, I can understand why you wouldn't want to copy types where std::mem::size_of<T>() > std::mem::size_of<usize>
. Is it just to reinforce the notion that we are returning a reference to a struct member? Or are there other practical reasons?
Also, if there is a better venue for asking these sorts of questions, I'd be happy to utilize that in the future.
Thank you for putting this together! I wish I would have had this when I started playing with Rust 3 or 4 years ago!
Hello, and thanks for writing a really great resource! I'm really having fun learning Rust with this!
I had to check the solution for this problem, since I was trying to create a method on the struct for validating title, description and status. But in the solution, I see that it is a private function, not method that is created. The instruction had me trying to add the validate functions within the impl Ticket
block.
So, unless I have misunderstood methods vs functions (which is entirely possible!) I suggest replacing the word methods
with functions
on the line I linked above.
I found it impossible to guess what structure was requested, and difficult to parse what the result actually means.
There's a lib.rs
, so I thought to make a pub(crate) hello_world()
there. (Wrong, IIUC because we're using the crate as though external, so it needs to be just pub
)
Next, I thought, OK, maybe we're making a new interior crate? But the text refers to cargo new
, not cargo init
, or creating any directories.
At this point, I'm just confused so I look at the solution.
Hi, thanks for the exercices, I enjoy them.
In exercice (06) ticket_management - (02) vec, you have to complete the following function:
pub fn fibonacci(n: u32) -> u32 {
// TODO: implement the `fibonacci` function
//
// Hint: use a `Vec` to memoize the results you have already calculated
// so that you don't have to recalculate them several times.
todo!()
}
Following the hint, I used a vec to memoize the results in a vec, but my code ended up a tiny bit too complex with some once_cell::sync::Lazy
and std::sync::Mutex
, which doesn't seem right for such an exercice. I have done memoization in Rust before, and it's not super fun, so that could explain why I used once_cell
stuff, but to me the hint was about caching the results between calls.
use once_cell::sync::Lazy;
use std::sync::Mutex;
static FIBONACCI_MEMO: Lazy<Mutex<Vec<u32>>> = Lazy::new(|| Mutex::new(vec![0, 1]));
pub fn fibonacci(n: u32) -> u32 {
let nusize = n as usize;
let mut memo = FIBONACCI_MEMO.lock().unwrap();
if memo.len() <= nusize {
if memo.capacity() <= nusize {
memo.reserve(nusize * 2);
}
std::mem::drop(memo);
let result = fibonacci(n - 2) + fibonacci(n - 1);
let mut memo = FIBONACCI_MEMO.lock().unwrap();
memo.push(result);
return result;
}
memo[nusize]
}
I looked at the solution and I see that it's much simpler.
pub fn fibonacci(n: u32) -> u32 {
let n = n as usize;
let mut memo = vec![0, 1];
for i in 2..=n {
memo.push(memo[i - 1] + memo[i - 2]);
}
memo[n]
}
But I'm not sure the Vec
is necessary at all:
pub fn fibonacci(n: u32) -> u32 {
if n <= 1 {
return n;
}
let mut a = 0;
let mut b = 1;
for _ in 2..=n {
let c = a + b;
a = b;
b = c;
}
b
}
Should the question on line 3 really be about &mut [str]
, not &mut str
? I mean, you can easily take an array here, but neither the tests nor the TODO indicate that you should.
I failed to pass assert!(err.source().is_some());
My thought:
in pub fn new(title: String, description: String, status: String) -> Result<Self, TicketNewError>{...}
own error is TicketNewError.
but inside this function, there is a change that ParseStatusError
will present.
so we need a code to convert it.
let status = match Status::try_from(status) {
Ok(s) => s,
Err(e) => return Err(TicketNewError::InvalidStatus{source: e}), // wrap to TicketNewError
};
in TickNewError
#[derive(Debug, thiserror::Error)]
pub enum TicketNewError {
...
#[error(transparent)] //sepcific use the source error???
InvalidStatus {source: ParseStatusError},
}
but do some the source will type of ParseStatusError not Some(ParseStatusError).
if I impl the std::error::Error
for TicketNewError
it will lead to conflict impl.
In the code example of section Data leakage is process-scoped the vector is leaked via Box::leak
which expects Box
. Shouldn't it be v.leak()
instead?
Hi, I'm trying to understand something about the solutions code. Can someone explain what is the purpose of the piece of code below or why is it required? I'm confused because even when I remove it, the tests still pass. All I know is that it's related to fixing a warning about something called op_ref on the last assert_eq!.
impl std::ops::Add<&SaturatingU16> for SaturatingU16 {
type Output = SaturatingU16;
fn add(self, rhs: &SaturatingU16) -> Self::Output {
self + *rhs
}
}
Any insights would be much appreciated! Thanks in advance!
As a Rust beginner it's nice to come across such great tutorials, thanks to the contributors.
I had an issue while reading section 3.7: link
&mut-setters have a downside: you can't chain multiple calls together. Since they don't return the modified Ticket instance, you can't call another setter on the result of the first one. You have to call each setter separately:
I was curious if it was possible to chain calls by returning &mut Self
, and after trying it seems to work.
playground link
gist link
So I'm not sure if there are downsides to this implementation that lead to this description in the book?
Test case u64_mut_ref_size implies that the reference whose size is being tested is mutable, but the reference in the actual test case is immutable, just like in two other test cases which do not contain the word mut in their names.
If this exercise is supposed to test whether somebody understands that immutable and mutable references have the same size in memory, then the type in that test case should be &mut u64.
#[cfg(test)]
mod tests {
use super::Ticket;
use std::mem::size_of;
#[test]
fn u16_ref_size() {
assert_eq!(size_of::<&u16>(), todo!());
}
#[test]
fn u64_mut_ref_size() {
assert_eq!(size_of::<&u64>(), todo!());
}
#[test]
fn ticket_ref_size() {
assert_eq!(size_of::<&Ticket>(), todo!());
}
}
I have this issue after upgrading my fork.
error: manifest path `exercises/04_traits/05_str_slice/Cargo.toml` does not exist
❌ (04) traits - (05) str_slice
I only noticed this because of https://rust-exercises.com/04_traits/05_trait_bounds.
Might be nice to have wr reset --chapter 4 --exercise 5
when the order of exercises changes?
In this task, I've completed the part where I add both features to the Status
enum. But after watching (https://www.youtube.com/watch?v=b0bgAYJDhhQ) i'm tempted to try out the code below:
impl TryFrom<Into<String>> for Status {
type Error = ParseStatusError;
fn try_from(value: impl Into<String>) -> Result<Self, Self::Error> {
let value: String = value.into();
match value.to_lowercase().as_str() {
"todo" => Ok(Status::ToDo),
"inprogress" => Ok(Status::InProgress),
"done" => Ok(Status::Done),
_ => unimplemented!(),
}
}
}
I ran into problems like "Into
can't become an object" and some others.
I also tried pub trait MyTrait: Into<String> + Sized {}
(because https://doc.rust-lang.org/std/convert/trait.TryFrom.html requires a subtrait Sized). But it looks like doing it the way I wanted is not possible.
Now i'm wondering, I'm not sure if it's just my approach that's wrong or it's just impossible.
Any insights and suggestions would be greatly appreciated.
With the test being annotated with
#[allow(overflowing_literals)]
in combination with the test being:
assert_eq!(255 as i8, todo!());
a naïve answer of 255_i8
succeeds.
I'm guessing the idea of this is that one should answer with -1_uX
but the person I was guiding through the exercises would have skipped right by if I hadn't said "this assertion will fail".
Maybe also relevant is that the overflow-checks profile, set on the workspace (as is necessary) from a couple of exercises ago is likely still there. If so, this set of tests might need one that "asserts" we turned it off again. (Though I suppose that interferes with the re-runnability of the tests.)
I'm loving this so far, but one issue I just ran into that made me look at the solutions was that modifying the Cargo.toml
under exercises/02_basic_calculator/08_overflow
wouldn't work when running wr
, and you had to modify the root level Cargo.toml
instead to get the expected overflow behavior.
As a beginner to Rust I was quite confused as to why this was failing/not working, so maybe specifying/clarifying how Cargo works exactly would be good before having people start modifying it? I had presumed that it would automatically pick up whatever is the closest Cargo.toml
to the tests, but apparently that isn't the case.
For example, it would be nice to understand why it is that every single exercise has its own Cargo.toml
, especially if it doesn't seem like these Cargo
files are being respected? This might be covered in a latter exercise, in which case maybe introducing it earlier would be good?
This could also be a setup/local issue but I doubt it, as it's a fresh Rust install and I just opened the entire repo/folder in RustRover.
The solution for implementing Index<&TicketId> is written like this:
impl Index<&TicketId> for TicketStore {
type Output = Ticket;
fn index(&self, id: &TicketId) -> &Self::Output {
&self[*id]
}
}
Here self
refers to the TicketStore {counter: u64, tickets: Vec}. So the index function should not work.
The fix should be to change it to
impl Index<&TicketId> for TicketStore {
type Output = Ticket;
fn index(&self, id: &TicketId) -> &Self::Output {
self.tickets.iter().find(|t| t.id == *id).unwrap()
}
}
which is the same implementation as Index
edits: fix code formatting
It seems like this exercise is incomplete. Is there anything I can do to add to the exercise?
I think 4.9 could benefit from an additional paragraph expanding on what type Output;
does.
In the solution for "4.13. Outro", there is the line type Output = SaturatingU16;
, which I think could be type Output = Self;
, since it is implemented for SaturatingU16
?
I guess my main point of confusion is, "Why do we need to create the Output type, when we can just specify the return type in the function signature below?" and "Why can't we just specify -> Self
in the function signature, why do we need -> Self::Output
?
I've been testing out 100ETLR and am generally really loving it - it hits at exactly the right balance of explanation / exercise / exploration for the people I'm hoping to teach with it.
One issue I ran into is that by by default cargo test
on exercises (e.g. 01_intro/00_welcome) produces warnings:
warning: function `greeting` is never used
--> exercises/01_intro/00_welcome/src/lib.rs:18:4
|
18 | fn greeting() -> &'static str {
| ^^^^^^^^
|
= note: `#[warn(dead_code)]` on by default
warning: `welcome_00` (lib) generated 1 warning
One of the big lessons I'm trying to impart on my students is that the Rust compiler has really, really good warnings and errors, and you should never ignore them because they almost always provide really high quality signal to point out a real problem you should address.
Accordingly, I'd much prefer if the code was free of benign warnings. How would y'all feel about making all of the top-level-just-for-tests functions pub
, so that these warnings get quashed?
Alternatively, we could #[allow(dead_code)]
them or similar, but that feels like it's more confusing and require more explanation...
So basically on the drop method i had to put this code :
fn drop(&mut self) {
*(*self.counter).borrow_mut() += 1;
}
Instead of this :
fn drop(&mut self) {
*self.counter.borrow_mut() += 1;
}
When trying the latter i was getting this error :
cannot use +=
on type Rc<RefCell<usize>>
Which make sense because Rc implements BorrowMut that return a mut reference to itself.
So when we call self.counter.borrow_mut() it returns a &mut Rc and then when we dereference we just get Rc so "+=" don't work.
You can tell me if i'm the only one that had issues using the solution on the Interior Mutability exercice.
I am currently working on the 03_modules
exercise and I found out about this crate cargo-modules which is quite nice. Perhaps it can be suggested as an optional resource?
Below, a usage example 👇🏼
❯ cargo modules structure --package modules
crate modules
├── struct Ticket: pub(crate)
│ └── fn new: pub(crate)
└── mod helpers: pub(crate)
└── fn create_todo_ticket: pub(self)
Exercise (07) threads - (05) channels
won't pass for me even when using the solution in the solutions
branch.
Error:
❌ (07) threads - (05) channels
Meditate on your approach and return. Mountains are merely mountains.
error: test failed, to rerun pass `--test insert`
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 2 tests
ready --- FAILED
.
failures:
---- ready stdout ----
thread 'ready' panicked at exercises/07_threads/05_channels/tests/insert.rs:31:5:
assertion failed: move_forward
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
ready
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.20s
Error with RUST_BACKTRACE=1
:
❌ (07) threads - (05) channels
Meditate on your approach and return. Mountains are merely mountains.
error: test failed, to rerun pass `--test insert`
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
running 2 tests
ready --- FAILED
.
failures:
---- ready stdout ----
thread 'ready' panicked at exercises/07_threads/05_channels/tests/insert.rs:31:5:
assertion failed: move_forward
stack backtrace:
0: rust_begin_unwind
at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library/std/src/panicking.rs:645:5
1: core::panicking::panic_fmt
at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library/core/src/panicking.rs:72:14
2: core::panicking::panic
at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library/core/src/panicking.rs:145:5
3: insert::ready
at ./tests/insert.rs:31:5
4: insert::ready::{{closure}}
at ./tests/insert.rs:22:11
5: core::ops::function::FnOnce::call_once
at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library/core/src/ops/function.rs:250:5
6: core::ops::function::FnOnce::call_once
at /rustc/9b00956e56009bab2aa15d7bff10916599e3d6d6/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
failures:
ready
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.20
The TODO assignment in (05) (09) calls for a default description of No description provided
, while the tests assert it to be Description not provided
.
In chapter 3 (ticket v1) task 2 (validation) you say the following:
// - the `title` should be at most 50 bytes long.
// - the `description` should be at most 500 bytes long.
The tests on the other hand state the following:
#[should_panic(expected = "Title cannot be longer than 50 characters")]
fn title_cannot_be_longer_than_fifty_chars() {
Ticket::new(overly_long_title(), valid_description(), "To-Do".into());
}
#[should_panic(expected = "Description cannot be longer than 500 characters")]
fn description_cannot_be_longer_than_500_chars() {
Ticket::new(valid_title(), overly_long_description(), "To-Do".into());
}
This would be incorrect, as utf-8 characters can be more than one byte in size. (think ä,ß, etc.)
It's a little tedious to go into each folder when you complete an exercise. Is there a better way to do this? Is there a way to make a better way to do this?
Hi! Just finished the exercise by find and replacing Mutex with RwLock, which compiled and ran.
Solution seems to indicate that was the move as well.
Shouldn't there be something in the exercise that forces you to use read
and write
, or is that not possible to check via the tests?
Hello,
I pulled the repo mid-May before 04_traits/05_trait_bounds
was added, I have now pulled the latest changes, but the wr
command seems to still seek 04_traits/05_str_slice
which doesn't exist anymore.
I'm unsure if the folder structure was hardcoded in the runner or if I need to do something on my end to have it recognize the new exercise order (I tried uninstalling the crate and reinstalling)
error: manifest path exercises/04_traits/05_str_slice/Cargo.toml
does not exist
Thank you!
Hi, First off thank you so much for this book. I was thinking, now that I have cloned this repository, is there any way to serve this git book locally? Like for example how you can open the rust book using rustup doc --book
.
How can I run 100-exercises-to-learn-rust hosted on my localhost via the Web.
I prefer the Web format version but I get
Playground Communication: timeout
The section Multithreaded updates says:
The same strategy won't work in the current multi-threaded version, because the mutable reference would have to be sent over a channel. The borrow checker would stop us, because &mut Ticket doesn't satisfy the 'static lifetime requirement of SyncSender::send.
But SyncSender::send has no 'static lifetime requirement. Maybe you had in mind 'static lifetime bound put on closure by thread::spawn, which prevents using refs.
I actually tried to send &mut Ticket over a channel in the exercise code. Compiler starts asking you to put lifetimes everywhere, it becomes a mess, I didn't succeed. So, yes, you can't send &mut Ticket over a channel, but for a reason different from the one stated in the text.
I also tried to send &mut over a channel in a single-threaded case. That works ok.
The Macros section of the book's 4.4 - Derive macros page has a link to a Further Reading section but the subsection is missing from the page.
BTW, this an excellent learning resource, thank you for putting it together!
I had unlocked an old exercice, chapter 4, ex 5 about string slices.
But by following the online book I noted that ex 5 is now about trait bounds. So I decided to update from origin and rebase my way back to where I was.
Now wr is stuck on a chapter 5 about string slices which doesn't exists, it's now chapter 6. How can I solve this?
❌ (04) traits - (05) str_slice
error: manifest path `exercises/04_traits/05_str_slice/Cargo.toml` does not exist
In the section "&str to String" there is a paragraph:
We've been primarily using .into(), though.
If you check out the implementors of Into you won't find Into<&str> for String. What's going on?
Is Into<&str> for String
really what we're looking for? That converts String into &str. But, according to the section header, we want the opposite - hypothetical Into<String> for &str
. No?
Hi, I'm interested to see this project translated to Spanish.
Do you want this? I can offer me as a volunteer to translate it.
I'm doing this with the official book of Rust (Translated version) and the Microsoft book, "Rust for C#/.Net Devs" (Translated version).
I know that the license that you are using allows me to do it, but I want a confirmation if you are interested to translate the book and if do you want to do it with our community RustLangES.
In What is a thread?
section there is a line:
Each thread has its own stack, instruction pointer, and program counter.
Aren't instruction pointer and program counter the same thing? E.g. in Wikipedia.
Hi! Learning rust with your course. Thanks a lot!
Shouldn't the following
If all fields are private, it is no longer possible to create a `Ticket` instance directly...
be
If __any field is__ private, it is no longer possible to create a `Ticket` instance directly...
meaning it's enough to have a single private field and you can't instantiate directly.
Two notes
panic
messages for the errors is a little annoyingTitle cannot be longer than 50 characters
, from https://doc.rust-lang.org/std/string/struct.String.html#method.lenReturns the length of this String, in bytes, not chars or graphemes. In other words, it might not be what a human considers the length of the string.
In the exercise it asks the 'title' should be at most 50 bytes long
04_traits/12_copy passes even without implementing wrapping, because 02_basic_calculator/08_overflow added it to dev profile in Cargo.toml.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.