The current borrow checker in rust considers only general use cases and doesn't allow the user to specify a specific use case which may be valid but not in general. How ? for example a struct that have a vec of references that must be valid as long as the struct is valid may be dclared as this:
trait Object { ... }
struct SomeSt {
objects: Vec<&' ??? dyn Object> // which lifetime to use ?
}
So which lifetime the objects must all satisfy ? the lifetime of self, but what is the lifetime of self ? it is 'static ! because rust allows all objects to move a struct that has no lifetime annotations is considered static since it can be moved to a static object. In order to limit the lifetime of a struct to a scope it is declared in the struct must have a reference to something in this scope. But if the struct has no references ? or has a collection of objects that must satisfy a lifetime but their lifetimes aren't the same ? The current way to do this is via PhantomData. Another solution is to allow immovable types but it won't be usable in rust without constructors. The idea I propose is to allow the user to pin a variable to the current scope so it has a hidden lifetime parameter to the current scope and introduce a lifetime 'self which is reinterpreted as follows: it is the sum of all lifetimes annotations of the struct including the the pin lifetime and if no lifetime annotations and no pin it is equivalent to 'static
example code using PhantomData:
use std::marker::PhantomData;
pub struct PinFlag;
pub const STATIC_PIN: PinFlag = PinFlag;
pub trait ExecutorOperation {
fn invoke(&self);
}
pub struct Executor<'selff> {
_pin: PhantomData<&'selff PinFlag>,
ops: Vec<&'selff dyn ExecutorOperation>,
}
impl<'selff> Executor<'selff> {
fn pinned(_: &'selff PinFlag) -> Self {
Self {
_pin: PhantomData,
ops: Vec::new()
}
}
fn run(&mut self) {
for op in self.ops.drain(..) {
op.invoke();
}
}
}
impl<'selff> Drop for Executor<'selff> {
fn drop(&mut self) {}
}
pub struct Operation {
id: i32
}
impl Operation {
fn new(id: i32) -> Self {
Self {
id: id
}
}
}
impl ExecutorOperation for Operation {
fn invoke(&self) {
println!("operation {} is invoked", self.id);
}
}
pub fn consume_static<T: 'static>(_t: T) {}
pub fn main() {
let static_ex = Executor::pinned(&STATIC_PIN);
consume_static(static_ex);
let scope_pin = PinFlag;
let op1 = Operation::new(1);
let op2 = Operation::new(2);
let mut ex1 = Executor::pinned(&scope_pin); // let mut pinned ex1 = Executor::new()
// static_ex = ex1; // error static_ex is required to be static but ex1 can't escape the current scope
ex1.ops.push(&op1);
ex1.ops.push(&op2);
{
let scope_pin = PinFlag;
let op3 = Operation::new(3);
let op4 = Operation::new(4);
let mut ex2 = Executor::pinned(&scope_pin); // let mut pinned ex2 = Executor::new()
ex2.ops.push(&op1);
ex2.ops.push(&op2);
ex2.ops.push(&op3);
ex2.ops.push(&op4);
//ex1 = ex2; // error ex2 is can't escape the current scope since it borrows scope_pin for the current scope
ex2.run();
}
ex1.run();
}