I am reading documents about tree borrow, and confused by the effect of tree borrow:
For example, the code below is refused by rustc, but I think is accepted by tree borrow
fn share_read(u: &mut &mut u8) {
let x = &mut *u; // x: Reserved
let y = &mut *x; // y: Reserved
println!("{}", **u); // child read for u && foreign read for y
println!("{}", **y); // child read for y
}
Reading should not affect the permissions of u, y, so the code is accepted by treeborrow, I think. Well, rustc does refuse the code.
If the reason is borrowck is more strict, then what is the point of the tree borrows, after all, the code is refused at the end.
If a theory can't be used to predicate weather a code can pass compiling or not, then it is useless at least for a normal programmer, and I have to keep my old understanding.
Tree borrows also applies to unsafe code using raw pointers. It is possible to introduce raw pointers and unsafe blocks to your example so that it compiles, and then tree borrows is what determines whether that unsafe code is okay or not.
(At least tree borrows is one proposal for how that should be determined.)
So, tree borrows take effect after rustc accepting some potential unsound unsafe code, to detect these unsafe and unsound code out?
The code accepted by tree borrow but refused by rustc still will keep to be refused by rustc, and will not be changed to be accepted in the near future or forever?
// cargo miri run # stack borrows accepts the code
// MIRIFLAGS=-Zmiri-tree-borrows cargo miri run # so as TB
fn main() {
let mut n = &mut 0;
unsafe { share_read(&mut n) };
share_read_minimize_unsafe(&mut n);
}
unsafe fn share_read(u: &mut &mut u8) {
let x = &mut *u as *mut &mut u8;
let y = &mut *x as *mut &mut u8;
// **y += 1; // even adding this line won't cause UB
println!("{}", **u);
println!("{}", **y);
}
fn share_read_minimize_unsafe(u: &mut &mut u8) {
let x = &mut *u;
let y = &mut *x as *mut &mut u8;
// unsafe { **y += 1 }; // even adding this line won't cause UB
println!("{}", **u);
println!("{}", unsafe { **y });
}
The question is more like asking if TB works, why we have a more strict borrowck.
Then the question also becomes if raw pointers have aliasing rules as references do, why will we have references and raw pointers to do the same thing? We can throw references or pointers away and leave the other kind .
The difference between tree borrows and borrowck is that borrowck is designed to be checked at compile-time, but tree borrows is not designed with that goal in mind.
This is why there is code that borrowck rejects, but tree borrows accepts. When you want to automatically check a set of rules, you must design the rules in a way that makes them easy to check in an automated manner. Borrowck is designed with that constraint in mind, which means that you ended up with a significantly more restrictive set of rules.
So really, there's no way to check whether code satisfies the tree borrows rules in an automated manner.
That said, the miri interpreter can execute code using tree borrows and detect violations of tree borrows at runtime. But unlike the borrow-checker, it's a runtime check. You have to actually trigger the bad codepath for it to detect the issue — unlike with the borrow-checker that detects the issue at compile-time.
Tree borrows is only a proposal for what the rules should be. It is not the final rule set.
Tree borrows only deals with rules about pointer provenance, but there are also other kinds of UB. For example, the rule that a bool must be either 0 or 1 is not a rule that comes from tree borrows. Code that violates that rule still has UB even if it doesn't break the tree borrows rules.
It doesn't really make sense to talk about whether code that Rust rejects has UB. UB is something that happens once you execute code, which you can't if it doesn't compile.
That's the core issue here. If you only care about compileability and don't care about the fact that your program doesn't work then TreeBorrows is entirely useless for you.
Now, if you want to actually run your program and be sure that program that is supposed to print some recipes wouldn't send all your private info to some nefarous guys… then ThreeBorrows becomes useful.
It's impossible to create a compiler which would both accept all useful programs and reject all badly written programs. Simply mathematically impossible (and it doesn't even matter what “useful” means here).
That's why Rust split all the programs in two:
Normal “safe” Rust: all programs are, well, “safe”, but certain design patterns are disallowed.
And unsafe Rust: all useful programs are permitted but now developer have to ensure that rules are not violated.
Which rules? Well, TreeBorrows is one contender (there are also earlier proposal, StackedBorrows). In safe rust borrow checked ensures that rules are not violated, in unsafe Rust it's your responsibility.
So the optimization in rustc will take tree borrows into account, normal rust code and the optimizer in rustc make a deal on the rule set. (ignoring its unstable at the moment)
I think any yes really should be qualified further based on what alice said above:
In the sense that if tree-borrows accepts the code then no undefined behavior was reached however, that does not necessarily mean that undefined behavior is unreachable through some other usage of the code.
That is to say, some code may allow the caller to produce UB without tree-borrows detecting it unless the caller actually provokes said UB. While a different caller might fail tree-borrows because it uses the broken API in a way that does.
Edit: I have an would be happy to produce an example of this if it helps, but I haven't tried to minimize it yet.