I was looking into Microsoft's Project Verona and I thought I could simulate the concept of regions in Rust.
//Project Verona code
var r1 = new Node;
var r2 = new Node in r1; //Same region as r1
var r3 = new Node in r1; //Same region as r1
r1 = new Node; //Removes original r1 and this prevents you to use r2 and r3
I thought: "Well, this is like having the same lifetime!" So I tried:
use core::marker::PhantomData;
#[derive(Debug)]
struct Test<'a>(i32,PhantomData<&'a()>);
fn func<'a>() {
let a = Test::<'a>(1,PhantomData);
let b = Test::<'a>(2,PhantomData);
println!("{:?}", a);
drop(a);
println!("{:?}", b);
}
fn main() {
func()
}
And this compiles....
So my question is: Why does this compile? Shouldn't b be unable to use after the drop of a?
Since you haven't provide the lifetime explicitly or give enough information to infer it at the caller side, the lifetime 'a becomes the fallback lifetime 'static.
My reasoning was that 'a becomes the lifetime of a because of the annotations, but that's not true than?
And is there way to explicitly set/annotate the lifetime of a en b?
Ah yeah, that's true, but how do I explicitly provide a lifetime?
Lifetime annotations are only an upper bound on how long a value can live. It is allowed to live for a shorter time than the annotation. Additionally since 'a is a lifetime parameter, it is chosen by the caller of func, not the implementor. You can't choose what 'a is inside foo for the same reason you can't choose that bar should be 20 in this:
fn func(bar: u32) {
// Try to force bar to be 20 by changing the body.
// bar = 20 doesn't count — it has to have been 20 from the beginning.
}
Parameter really means parameter. I can call your function with func::<'static>(), and it will have to work with 'a = 'static.
So yeah, my attempt to simulate regions miserably failed, because my reasoning was wrong and Test<'a> does not have 'a as lifetime.
But why can't we just annotate the lifetime of variables?
Like this:
let a'a = Test(1);
let b'a = Test(2);
As far as I understand, the compiler give every variable a lifetime, so why not give the ability to share a lifetime?
It's important to understand that lifetimes don't change the behaviour of the code — in fact the mrustc project is able to correctly compile all Rust code without looking at the lifetimes at all. Lifetimes are used only to reject invalid code, and not to change the code's behaviour.
You can do that, just using different terminology:
#[derive(Debug)]
struct Bar<'a>(PhantomData<&'a ()>);
impl<'a> Bar<'a> {
pub fn new() -> Self {
Bar(PhantomData)
}
pub fn from_other(other: &'a Bar<'_>) -> Self {
Self::new()
}
}
let a = Bar::new();
let b = Bar::from_other(&a);
drop(a);
println!("{:?}", b); // Uh oh!
Instead of you defining the regions explicitly, you tell the compiler how they're linked and the compiler solves to see if it's invalid. It's less "give the calculator an expression and see the result" and more of "solve the equation given any variables x, y, z, etc.".
Yeah this is probably the best way to simulate it then.
However your piece of code would not be a region, but rather b depends on a.
For a region you would have to make a also dependent on b.
It's not exactly a region, but it seems like the closest thing we have in Rust.
Also a side note, I also read a bit more about regions in project verona and from what I got from it, is that whatever happens inside a region, can be unsafe, so I don't want that in safe Rust.