This will be a living document where I will ocassionally add new "gotchas" I discover with Rust. Do note that I am still learning Rust. I mean, who isn't?
8 May 2021.
Dear diary,
I must never rely on Drop
in uncontrolled code to keep our datastructures in a consistent state.
This is because std::mem::forget
is safe and it is easy to leak memory using Rc
cycles (and probably through many other non-trivial ways).
See http://cglab.ca/~abeinges/blah/everyone-poops/
Truly a Leakpocalypse,
Ernest
8 May 2021.
Dear diary,
Generic types that implement Drop
has an additional lifetime requirement that all generic parameters must strictly outlive the type. However, there is an escape hatch where this check is skipped, and future versions of Rust seem to want to perform this check automatically, where this stricter lifetime requirement is applied whenever the references are observed during the Drop
. This means that the compatibility of an interface might be tied to the implementation of the type, not just the interface alone.
See https://doc.rust-lang.org/nomicon/dropck.html
More info... but these lists don't mention Drop
??
- https://doc.rust-lang.org/cargo/reference/semver.html
- https://stackoverflow.com/questions/41185023/what-exactly-is-considered-a-breaking-change-to-a-library-crate
- https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md
Update (8 May) - This is noted in rust-lang/cargo#8736. Good, I can rest well now.
In the name of SemVer,
Ernest
9 May 2011.
Dear diary,
One cool thing ability of mutable references is that it allows you to assign a different value to that target memory location, referred to as a "direct mutation to the [memory] path" in Niko's talk on Polonius. This is useful in allowing, for example, elements in a Vec
to be replaced in-place.
However, sometimes I have a struct containing a field that has a complicated interface, and I want to expose that interface to the user of the struct. It is tempting to do this:
pub struct Inner {
x: i32,
}
impl Inner {
pub fn new() -> Self {
Inner { x: 0 }
}
pub fn some_mut_method(&mut self) {
self.x += 1;
}
}
pub struct Outer {
inner: Inner,
}
impl Outer {
pub fn inner(&mut self) -> &mut Inner {
&mut self.inner
}
}
so that you can then call outer.inner().some_mut_method()
. However, this allows outside code to also call *outer.inner() = new Inner()
, which may not be what you want to allow as it may mess up the consistency of Outer
's state.
To prevent this, either
- Re-expose the
Inner
's APIs ontoOuter
and get rid of the reborrowing method that returns&mut Inner
. - Make
Inner::new
private and make sure it's not possible to get an instance ofInner
anywhere. - (My personal favourite solution) Wrap the
&mut Inner
in a new-type:
struct Inner {
x: i32,
}
impl Inner {
pub fn new() -> Self {
Inner { x: 0 }
}
pub fn some_mut_method(&mut self) {
self.x += 1;
}
pub fn some_method(&self) -> i32 {
self.x + 2
}
}
pub struct InnerRef<'a>(&'a Inner);
pub struct InnerRefMut<'a>(&'a mut Inner);
impl InnerRef<'_> {
pub fn some_method(&self) -> i32 {
self.0.some_method()
}
}
impl InnerRefMut<'_> {
pub fn some_method(&self) -> i32 {
self.0.some_method()
}
pub fn some_mut_method(&mut self) -> i32 {
self.0.some_mut_method()
}
}
pub struct Outer {
inner: Inner,
}
impl Outer {
pub fn inner(&self) -> InnerRef {
InnerRef(&self.inner)
}
pub fn inner_mut(&mut self) -> InnerRefMut {
InnerRefMut(&mut self.inner)
}
}
Paranoid lender,
Ernest
16 May 2021.
Dear diary,
Today I learned that LLVM (which rust uses) makes infinite loops invoke undefined behaviour if the loop does not contain any side effect, allowing LLVM to fully remove the infinite loop and generate incorrect programs:
Turns out this is because C/C++ compilers like to optimise multiple terminating loops into a single loop, but it can only do that correctly if the comiler can prove that those loops will indeed terminate. But since it is nearly impossible for the compiler to prove that with the limited information it has, the compiler decides to assume that the loops will terminate instead.
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1528.htm
(The exception is that in C, the loops are allowed to be bound by a constant expression condition)
Turns out non-termination should be treated like a side-effect.
Fortunately, this is solved very recently (March 5) rust-lang/rust#81451 Wohoo!
Non-terminating observer, Ernest