pretzelhammer / rust-blog Goto Github PK
View Code? Open in Web Editor NEWEducational blog posts for Rust beginners
License: Apache License 2.0
Educational blog posts for Rust beginners
License: Apache License 2.0
While it's true and can be presented with generics, maybe a note that the following is possible would be beneficial, so nobody erroneously extrapolates the rule:
struct Foo();
trait Trait {}
impl Trait for Foo {}
impl Trait for &Foo {}
impl Trait for &mut Foo {}
Like with impl Read for TcpStream
and impl Read for &TcpStream
.
I'm trying to understand how leaking works in one of the &'static
examples.
It's possible to generate random dynamically allocated data at run-time and return 'static references to it at the cost of leaking memory.
use rand;
// generate random 'static str refs at run-time
fn rand_str_generator() -> &'static str {
let rand_string = rand::random::<u64>().to_string();
Box::leak(rand_string.into_boxed_str())
}
Part of my confusion comes from the Box::leak
method. The documentation says that it returns a reference to the boxed value. If a reference is returned, that means the data is still there and the program hasn't "forgotten" about it. Maybe I'm misunderstanding what it means to "leak" memory. Can you help me understand?
There's no good reason for this discrepancy. Closures were first implemented with different type inference semantics than functions and now we're stuck with it forever because to unify them at this point would be a breaking change.
Would it be a breaking change? I'm struggling to come up with a example of a closure that currently compiles that would no longer compile if the elision rules were changed to match functions. A one-line example of such a closure would be illustrative and unobtrusive here. And in the event that the claim is mistaken, that would bode well for changing the language to remove this discrepancy.
https://github.com/pretzelhammer/rust-blog/blob/master/posts/common-rust-lifetime-misconceptions.md
#[derive(Debug)]
struct NumRef<'a>(&'a i32);
impl<'a> NumRef<'a> {
// no more 'a on mut self
fn some_method(&mut self) {}
// above line desugars to
fn some_method_desugared<'b>(&'b mut self){}
}
fn main() {
let mut num_ref = NumRef(&5);
num_ref.some_method();
num_ref.some_method(); // compiles
println!("{:?}", num_ref); // compiles
}
It seems you simply give the solution (let compiler handle the lifetime). but why?
what the lifetime 'a in struct NumRef doing/restricting? what is lifetime 'a in fn main ? (desugar lifetime in fn main maybe helpful?)
Thanks.
I'm confused and wondering why are the results so different 🤔
I'm using the latest nightly, which is 1.54.0 and node 16.0.0. All tests are just for "Read".
So I tested the same code in master on my local machine, which is: Desktop Linux, i7 3GHz, 8 core (8 thread), 16GB memory 2667MHz
SA
Requests [total, rate, throughput] 1060260, 17670.99, 17670.37
Duration [total, attack, wait] 1m0s, 1m0s, 2.106ms
Latencies [min, mean, 50, 90, 95, 99, max] 334.431µs, 2.219ms, 2.072ms, 2.965ms, 3.393ms, 5.35ms, 48.375ms
Bytes In [total, mean] 202156240, 190.67
Bytes Out [total, mean] 0, 0.00
Success [ratio] 100.00%
Status Codes [code:count] 200:1060260
DR
LATEST THROUGHPUT INFO
Requests [total, rate, throughput] 588250, 9804.16, 9803.81
Duration [total, attack, wait] 1m0s, 1m0s, 2.135ms
Latencies [min, mean, 50, 90, 95, 99, max] 373.671µs, 3.867ms, 3.614ms, 5.928ms, 6.807ms, 8.765ms, 31.832ms
Bytes In [total, mean] 112159696, 190.67
Bytes Out [total, mean] 0, 0.00
Success [ratio] 100.00%
Status Codes [code:count] 200:588250
PEM
LATEST THROUGHPUT INFO
Requests [total, rate, throughput] 650226, 10837.10, 10836.52
Duration [total, attack, wait] 1m0s, 1m0s, 3.258ms
Latencies [min, mean, 50, 90, 95, 99, max] 421.593µs, 3.662ms, 3.206ms, 6.278ms, 7.571ms, 10.887ms, 135.706ms
Bytes In [total, mean] 124626650, 191.67
Bytes Out [total, mean] 0, 0.00
Success [ratio] 100.00%
Status Codes [code:count] 200:650226
I ran it also on another machine, Laptop Linux, i9 2.4GHz, 8 core (16 thread), 32GB memory 3200MHz, and faced completely different result:
SA
LATEST THROUGHPUT INFO
Requests [total, rate, throughput] 1101823, 18363.66, 18363.24
Duration [total, attack, wait] 1m0s, 1m0s, 1.366ms
Latencies [min, mean, 50, 90, 95, 99, max] 365.511µs, 2.101ms, 1.935ms, 2.995ms, 3.492ms, 4.689ms, 43.751ms
Bytes In [total, mean] 210080948, 190.67
Bytes Out [total, mean] 0, 0.00
Success [ratio] 100.00%
Status Codes [code:count] 200:1101823
DR
LATEST THROUGHPUT INFO
Requests [total, rate, throughput] 218746, 3645.42, 3645.34
Duration [total, attack, wait] 1m0s, 1m0s, 1.195ms
Latencies [min, mean, 50, 90, 95, 99, max] 536.327µs, 10.683ms, 10.52ms, 20.746ms, 24.02ms, 29.712ms, 136.249ms
Bytes In [total, mean] 41707600, 190.67
Bytes Out [total, mean] 0, 0.00
Success [ratio] 100.00%
Status Codes [code:count] 200:218746
PEM
LATEST THROUGHPUT INFO
Requests [total, rate, throughput] 656688, 10944.79, 10944.22
Duration [total, attack, wait] 1m0s, 1m0s, 3.115ms
Latencies [min, mean, 50, 90, 95, 99, max] 553.575µs, 3.541ms, 3.302ms, 5.437ms, 6.413ms, 9.094ms, 162.15ms
Bytes In [total, mean] 125865200, 191.67
Bytes Out [total, mean] 0, 0.00
Success [ratio] 100.00%
Status Codes [code:count] 200:656688
Now I have two questions:
1- Why is it completely different from what we have here in the blog post?
2- Why DR is so much different on these two machines?! (I ran them several times, it's almost the same)
There's an actual difference between fn get_str<'a>() -> &'a str;
and fn get_str() -> &'static str;
. There wasn't until rust-lang/rust#42417 got merged, but since then <'a>
will work in some cases where a simple 'static
will not.
The "Kelvin / Celcius / Fahrenheit" example for PartialEq isn't right, as floating point numbers suck.
I expected to break transitivity, but it turns out even symmetry is broken fairly easily, see for example:
To fix, while much less fun, maybe do millimeters, centimeters, kilometers? If they are each stored as i32, you could make the millimeters in i64 for comparison, which deals with overflow.
TL;DR The misconception that if T: 'static then T must be valid for the entire program ... is not a misconception. It's literally true, in a sort of pedantic way.
The problem is that people conflate validity of types with lifetime of values.
The fact that a type is valid for an entire duration of a program is disctinct from whether or not there are values of that type physically existing at some point or another.
A type T: 'static
is valid even before you create any values of it, in fact, it's valid even if it's absolutely impossible to create any values of it, such as with the !
or Infallible
or similar types.
On the other hand, a non-static type can only be referred to in some part of a program where the type contains some specific lifetime, which only exists in that part of the prorgam. Again, this is distinct from any actual values of that type - those might not exist either.
As a consequence, leaking is completely unrelated to whether a type is 'static
or not, since leaking deals with values. You can leak values of non-static types just fine. I'm afraid mentioning leaking in that section only increases the confusion.
Here's a demonstration of this:
Hopefully this'll make sense. It's kind of hard to show this in an actual code, since I don't think there's a way of implementing functions for a non-static type only (there's no non-static marker trait etc.).
Hey, I just started reading the too many brainfuck compilers article and it is very interesting, however, I immediately noticed a code smell I would instantly refactor - it is subjective and I may be wrong by making a whole issue about it, but here it is:
So you have a code that looks like:
pub enum Inst {
IncPtr(usize),
DecPtr(usize),
IncByte(usize),
DecByte(usize),
WriteByte(usize),
ReadByte(usize),
LoopStart(usize, usize),
LoopEnd(usize, usize),
}
Then you spend extra 3 lines of the article documenting explaining what are these usizes for, but this can be easily made into a self-documented and much more readable code (and at places where you use it too).
For example (with compact formatting here in the issue):
pub enum Inst {
IncPtr { by: usize },
DecPtr { by: usize },
IncByte { by: usize },
DecByte { by: usize },
WriteByte { times: usize },
ReadByte { times: usize },
LoopStart { times: usize, end_idx: usize },
LoopEnd { times: usize, start_idx: usize },
}
The run-length encoding is way too repetitive and by definition is in each enum variant, so it can be abstracted away in a struct:
pub struct Inst {
pub kind: InstKind,
pub times: usize,
}
pub enum InstKind {
IncPtr,
DecPtr,
IncByte,
DecByte,
WriteByte,
ReadByte,
LoopStart { end_idx: usize },
LoopEnd { start_idx: usize },
}
This improves readability and usability by a lot with no downsides (not like there is or will be 7 layers of structs/enums), and I honestly don't really know why tuple structs even exist at all. As I've said, this is subjective, feel free to ignore this, it just immediately tingled me when I was reading your article (didn't even read past this yet, but it seems very-very interesting).
First off, amazing article! Thanks so much for writing it, it looks like a great reference and I've learnt way more than I probabaly need to know 😅.
In the Read & Write section you mention the Generic blanket impls. You already mention this in the Scope section, but I think it's worth reiterating that Read & Write aren't in the prelude so if you want to use these blanket impls you need to bring them into scope. I think it's a common enough gottcha that a lot of people would be thankfull to be reminded.
In 9) downgrading mut refs to shared refs is safe the last example starts with a block commented with // drop the returned mut Player refs since we can't use them together anyway
. Commenting out this block still allows the code to compile (playground), and its purpose is unclear to me. I think it might be better to just remove it?
I enjoy reading your posts!
Maybe make a release after each post is published so that we can subscribe to it?
Thanks!
Quite self-explanatory title. I want to translate one of your blog posts and publish it on a IT platform, namely habr.com, however, I am not sure if I am allowed to do so. Here are the relevant parts of Habr user agreement at the moment (emphasis mine):
4.8. By accepting the terms and conditions of this Agreement, the User shall provide Habr with a free (non-exclusive) license for using the Content in the following ways:
<...>
- to distribute copies of the Content, i.e. to provide access to material reproduced in any material form, including through network and otherwise, as well as by selling, renting, leasing, lending, including importation for any of these purposes (right of distribution);
<...>
- right to assign all or part of the received rights to third parties (the right to sublicense).
<...>
4.11. The User guarantees the right to use the Content under the terms and conditions of the aforementioned license to the extend required.
Since you didn't specify a license for posts themselves, you still have the exclusive rights for all your content, thereby I can't even legally publish a translation, not only on Habr but anywhere.
tnanks the series high quality Rust article!!!
Great post! I've needed a link for some of these before (number 11 is the most recent one I remember needed).
One nuance you might consider adding to misunderstanding 4 -- people might be conflating "generics" and "monomorphization" in their mind. So they'd be correct that it's possible to avoid monomorphization, just incorrect that it's possible to avoid generics.
Here are a few extra things I wish I knew when I started messing with Rust's iteration traits, in addition to what your tour of Rust traits already contains:
Iterator
is one of those few traits where you should really consider overloading the default implementation of some methods (size_hint, nth...), as it is suboptimal in many cases.Extend
is a thing and FromIterator
should almost always be implemented in terms of it.In Misconception-1: T only contains owned types. It is mentioned that:
trait Trait {}
impl<T> Trait for T {}
impl<T> Trait for &T {} // compile error
impl<T> Trait for &mut T {} // compile error
The above is True , but why does this work for concrete types:
#[derive(Copy, Clone)]
struct X {
val: i32,
}
trait T {}
impl T for X {}
impl T for &X {}
impl T for &mut X {}
This compiles fine .
So concrete types are special ?. Aren't generic types monomorphised to concrete types, i am confused.
According to the readme, the license applies to the code examples, but not whole blog posts, so I have to ask:
Am I allowed to copy your blog post from 2020-05-19 to the Rust wiki and quote you as the source?
Should I remain this in translation?
This section starts with "The Sized trait in Rust is an auto trait". However, the compiler disagrees.
Code:
trait Foo {}
fn foo(_: Box<dyn Foo + Sized>) {}
Output:
error[E0225]: only auto traits can be used as additional traits in a trait object
--> src/lib.rs:3:25
|
3 | fn foo(_: Box<dyn Foo + Sized>) {}
| --- ^^^^^ additional non-auto trait
| |
| first non-auto trait
|
= help: consider creating a new trait with all of these as super-traits and using that trait here instead: `trait NewTrait: Foo + Sized {}`
= note: auto-traits like `Send` and `Sync` are traits that have special properties; for more information on them, visit <https://doc.rust-lang.org/reference/special-types-and-traits.html#auto-traits>
In the section on Clone the description for function guarantee_length is as below:
Clone can also be useful in constructing instances of a type within a generic context. Here's an example from the previous section except using Clone instead of Default:
fn guarantee_length<T: Clone>(mut vec: Vec<T>, min_len: usize, fill_with: &T) -> Vec<T> {
But this function differs from the example in previous section in the method signature too as this takes an additional param fill_with
. The signature of the function in section Default is as below:
fn guarantee_length<T: Default>(mut vec: Vec<T>, min_len: usize) -> Vec<T> {
Given this difference, I think the description for function gurantee_length in section Clone can be updated, as it differs from the previous example by taking an additional param. One way I can think of is:
Clone can also be useful in constructing instances of a type within a generic context. Here's the modified example from the previous section which makes use of Clone instead of Default:
P.S: Thank you for your excellent guides which provide a lucid explanation of many of the Rust concepts.
In the blog post about lifetimes, the following code snippet is used:
static BYTES: [u8; 3] = [1, 2, 3];
static mut MUT_BYTES: [u8; 3] = [1, 2, 3];
fn main() {
MUT_BYTES[0] = 99; // compile error, mutating static is unsafe
unsafe {
MUT_BYTES[0] = 99;
assert_eq!(99, MUT_BYTES[0]);
}
}
There has been some controversy surrounding this feature (internals, blog), some even calling this a "misfeature" in dire need of replacement. Because they appear deceptively easy to use correctly (I think it's unproblematic here, given that indexing an array is a built-in, and not a call to index_mut
which AFAIK is UB), perhaps at least a warning should be added that this is non-trivial.
In the TryFrom
section, is it worth mentioning that you cannot implement both From
and TryFrom
since if From
is implemented for a given type T, an infallible TryFrom
is also auto-implemented for you. Therefore you end up with conflicting trait implementations for the same type. For example, let's say you implement both From
and TryFrom
for Point
, you get the following error:
error[E0119]: conflicting implementations of trait `TryFrom<(i32, i32)>` for type `Point`
--> src/main.rs:22:1
|
22 | impl TryFrom<(i32, i32)> for Point {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: conflicting implementation in crate `core`:
- impl<T, U> TryFrom<U> for T
where U: Into<T>;
For more information about this error, try `rustc --explain E0119`.
error: could not compile `foo` due to previous error
I would want to read your "customer's review" after 1 and half years later you started blogging about Rust.
Questions to be answered in the article from my point of view:
unsafe
becomes a disappointment since the sales pitch was about safety.PS: I've met with your blog through Learning Rust in 2020 while I was looking for upper-beginner content in Rust. The article is great but some parts might be outdated as there are updates on mentioned platforms.
The list of common misconceptions contains:
if you've ever written a struct method ... then your code has generic elided lifetime annotations all over it.
But that's not always true right?
struct A {}
impl A {
fn method(self) -> A {
self
}
}
pub fn main() {
let a = A {};
a.method();
}
I really enjoyed reading your Lifetimes misconceptions
post, thank you! Recently I stumbled upon Polonious talk given by Niko Matsakis. So I though it would be worth to mention about it as it's related to lifetime concept.
In the blog post tour of rust standard library traits, I feel the paragraph of asymmetry
requirements in partial_cmp
is not right:
All
PartialOrd
impls must ensure that comparisons are asymmetric and transitive. That means for alla
,b
, andc
:
a < b
implies!(a > b)
(asymmetry)a < b && b < c
impliesa < c
(transitivity)
In the document for PartialOrd, there is no part about asymmetry
. The requirements for partial_cmp
is:
The comparison must satisfy, for all
a
,b
andc
:
- transitivity:
a < b
andb < c
impliesa < c
. The same must hold for both==
and>
.- duality:
a < b
if and only ifb > a
.
Maybe the document is updated and we should catch up?
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.