This question already has answers here:
Is this error due to the compiler's special knowledge about RefCell?
(1 answer)
How does the Rust compiler know `Cell` has internal mutability?
(3 answers)
Closed 2 years ago.
In Unsafe Code Guidelines Reference, it says
All interior mutation in Rust has to happen inside an UnsafeCell, so all data structures that have interior mutability must (directly or indirectly) use UnsafeCell for this purpose.
Also, in a discussion about UnsafeCell, it says
UnsafeCell is basically an optimization barrier to the compiler.
It is true that UnsafeCell acts as a compiler optimization barrier in Rust? If yes, which line in the standard library source code emits a barrier and how does it work?
[UPDATE]
The answer of a related question gives a very nice explanation. The TL;DR version is: UnsafeCell<T> is marked with #[lang = "unsafe_cell"] which forces it to be invariant over T.
Now I think this is not very much connected to optimization, but interacts more closely with lifetime analysis.
For the notion of variance in Rust, The Rustonomicon Book gives a detailed explanation.
Related
This question already has answers here:
What does the exclamation point mean in a trait implementation?
(3 answers)
Closed 9 months ago.
I am unable to locate the documentation for !Unpin referred to here in the docs.
More generally, the ! operator seem to lack corresponding documentation regarding traits. Specifically, it seems to represent Not as in Not Unpin or perhaps Not Unpinable in this case. I suppose it is different from Pin in some way otherwise it would be redundant. Currently, searching for the documentation is challenging since ! occurs so frequently otherwise.
It would be good if the operator behavior of ! on traits could be included in Appendix B: Operators and Symbols of the docs.
Unpin is one of several auto-traits, which are implemented automatically for any type that's compatible with it. And in the case of Unpin, that's, well, basically all of the types.
Auto-traits (and only auto-traits) can have negative implementations written by preceding the trait name with a !.
// By default, A implements Unpin
struct A {}
// But wait! Don't do that! I know something you don't, compiler.
impl !Unpin for A {}
Unpin, specifically, indicates to Rust that it is safe to move values of the implementing type T out of a Pin. Normally, Pin indicates that the thing inside shall not be moved. Unpin is the sort of opposite of that, which says "I know we just pinned this value, but I, as the writer of this type, know it's safe to move it anyway".
Generally, anything in Safe Rust is Unpin. The only time you'd want to mark something as !Unpin is if it's interfacing with something in another language like C. If you have a datatype that you're storing pointers to in C, for instance, then the C code may be written on the assumption that the data never changes addresses. In that case, you'd want to negate the Unpin implementation.
This question already has answers here:
Is this error due to the compiler's special knowledge about RefCell?
(1 answer)
How does the Rust compiler know `Cell` has internal mutability?
(3 answers)
When I can use either Cell or RefCell, which should I choose?
(3 answers)
Situations where Cell or RefCell is the best choice
(3 answers)
Closed 9 months ago.
The community reviewed whether to reopen this question 9 months ago and left it closed:
Original close reason(s) were not resolved
What are the exact differences between Cell, RefCell, and UnsafeCell?
RefCell
Let's start with RefCell, which in my personal experience is the most commonly used of the three.
Normally, in Rust, borrowing rules are checked at compile-time. You have to prove to the compiler (effectively, to a type-checker-like system called the borrow checker) that everything you're doing is fair game: that every object is either mutably borrowed once or immutably borrowed several times, and that those two threads don't cross. RefCell moves this check to runtime. You can convert a RefCell<T> to a &T with borrow and you can convert a RefCell<T> to &mut T with borrow_mut[1].
When you call these functions, at runtime, Rust checks that the usual borrow rules apply. You don't have to prove to the type checker that it works, but if it turns out that you're wrong, your program will panic[2]. We haven't changed the rules; you've just shoved them from compile time to runtime. This can be useful, for instance, if you're writing a recursive algorithm that manipulates data in some complicated way, and you've personally checked that the borrow rules are followed but you can't prove it to Rust.
Cell
Then there's Cell. A Cell is similar but it's not reference-based. Instead, the fundamental operation of a Cell is replace. replace takes a new value for the cell (by value) and, as the name implies, replaces the contents of the cell. No mutability checks need to be done in this case, since this operation happens in one fell swoop. You call the function, the function does its magic, and it returns. It's not giving you a reference back. There are other functions on Cell, like update in Nightly Rust, but they're all implemented in terms of replace.
Note that none of the types we're talking about can be shared across threads. So, in the case of Cell, there's no concern that one thread is trying to replace the value while another reads it. And in the case of RefCell, there's no need for a complicated locking mechanism to coordinate the runtime checks.
UnsafeCell
Enter UnsafeCell. UnsafeCell is RefCell with all of the safety checks removed. We haven't pushed safety off to the runtime; we've taken it in the backyard and shot it. The fundamental operation of UnsafeCell is get, which is like borrow_mut for RefCell except that it always returns a mutable pointer. It won't fail, it won't complain about race conditions or shared data, it will just give you a pointer. Note that *mut T is a raw pointer type, and the only way to modify a pointer of that type is with Unsafe Rust.
UnsafeCell should not be used directly. It's the compiler primitive used to implement the other two (safe) cell types. If you do decide that you need UnsafeCell for some reason, you need to be very careful to preserve compiler assumptions about data access, because it's very easy to make things go very wrong when you start dipping into Unsafe Rust. Trust me, I speak from experience on this. Last time I unsafe'd some of my code, it started causing enough problems that I eventually rewrote the whole thing using (safe) primitives and never actually figured out what was going wrong. It gets messy fast.
[1] You're actually converting a RefCell<T> to specialized types called Ref<'_, T> and RefMut<'_, T>, respectively, but those types are designed to act like ordinary references, so I omit that detail for brevity.
[2] There are variants of these functions called try_borrow and try_borrow_mut which return a Result rather than panicking on failure.
I've recently started learning Rust and just learned about the Smart Pointers (Box, Rc and RefCell).
In the guide they talked about Rc implementing "shared ownership". But if I understood it correctly, the whole point of the ownership system is that there can only be one owner.
And to me (still a Rust newbie) it seems as if Rc and RefCell take ownership of they value they contain and just "expose" different types of references to the contained value?
Am I wrong and if yes: why is Rust allowed to "cheat" the ownership system like that and would I be theoretically able to implement my own "cheating" types?
if I understood it correctly, the whole point of the ownership system is that there can only be one owner.
No. Rust guarantees that there can be no more than a single mutable borrow and there cannot be mutable and non-mutable borrows at the same time. It doesn't say anything about owners.
why is Rust allowed to "cheat" the ownership system
It doesn't.
would I be theoretically able to implement my own "cheating" types
Yes. Those types are all implemented in Rust¹. Those types are battle-tested and perfectly safe under Rust's safety rules, but they require the use of unsafe at a lower level.
Note that unsafe doesn't permit going around the rule that you can have one mutable borrow XOR any number of non-mutable borrows, but using unsafe, you could do it anyway. This, of course, would actually be unsafe (and trigger undefined behavior).
1: Although some of those types are implemented using features that are still private to the compiler so you wouldn't be able to do everything as efficiently as the standard library, and Box and UnsafeCell are special to the language and cannot be reproduced by a normal library. There are for example many crates providing Rc or Arc alternatives which are better that the standard ones in some cases.
This question already has answers here:
Why are explicit lifetimes needed in Rust?
(10 answers)
Closed 2 years ago.
How come Rust does not fully infer ownership of its variables? Why are annotations needed?
If that were even possible I believe it would be a terrible user experience because:
if the compiler cannot deduce ownership of an object, the error can barely be understood (like with trial-and-error approach in C++ templates link);
the ownership policy doesn't seem to be easy to grasp (that's one opinion though) and trying to understand which semantic has been chosen by a compiler may lead to unexpected behaviors (reference a Javascript weird type conversions);
more bugs during refactoring can be introduced (implied by the point above);
full program inference would definitely take a huge amount of time, if it is even a solvable problem.
However, if you struggle with a lack of polymorphism, it is usually possible to parametrize a method with an ownership kind, which might be considered a somewhat explicit alternative to inference, e.g.:
fn print_str(s: impl AsRef<str>) {
println!("{}", s.as_ref());
}
fn main() {
print_str("borrowed");
print_str("owned".to_owned());
}
I am reading the Rust Book and everything was pretty simple to understand (thanks to the book's authors), until the section about lifetimes. I spent all day, reading a lot of articles on lifetimes and still I am very insecure about using them correctly.
What I do understand, though, is that the concept of explicit lifetime specifiers aims to solve the problem of dangling references. I also know that Rust has reference-counting smart pointers (Rc) which I believe is the same as shared_ptr in C++, which has the same purpose: to prevent dangling references.
Given that those lifetimes are so horrendous to me, and smart pointers are very familiar and comfortable for me (I used them in C++ a lot), can I avoid the lifetimes in favor of smart pointers? Or are lifetimes an inevitable thing that I'll have to understand and use in Rust code?
are lifetimes an inevitable thing that I'll have to understand and use in Rust code?
In order to read existing Rust code, you probably don't need to understand lifetimes. The borrow-checker understands them so if it compiles then they are correct and you can just review what the code does.
I am very insecure about using them correctly.
The most important thing to understand about lifetimes annotations is that they do nothing. Rather, they are a way to express to the compiler the relationship between references. For example, if an input and output to a function have the same lifetime, that means that the output contains a reference to the input (or part of it) and therefore is not allowed to live longer than the input. Using them "incorrectly" means that you are telling the compiler something about the lifetime of a reference which it can prove to be untrue - and it will give you an error, so there is nothing to be insecure about!
can I avoid the lifetimes in favor of smart pointers?
You could choose to avoid using references altogether and use Rc everywhere. You would be missing out on one of the big features of Rust: lifetimes and references form one of the most important zero-cost abstractions, which enable Rust to be fast and safe at the same time. There is code written in Rust that nobody would attempt to write in C/C++ because a human could never be absolutely certain that they haven't introduced a memory bug. Avoiding Rust references in favour of smart pointers will mostly result in slower code, because smart pointers have runtime overhead.
Many APIs use references. In order to use those APIs you will need to have at least some grasp of what is going on.
The best way to understand is just to write code and gain an intuition from what works and what doesn't. Rust's error messages are excellent and will help a lot with forming that intuition.