I'm sorry if the title is unclear - I couldn't figure a better one.
Also my apologies for my English or if my terminology isn't exact, I'm not a native speaker (open to any feedback.)
TL;DR: Here's reproducible example in the playground..
The problem
- A
contextvalue has themake_scopemethod which receives an input value. - The input value implements the
Begintrait whichbeginmethod consumes it and returnsendvalue which implements theEndtrait. - When the new
endvalue is created, some of the borrows of the input value are supposed to be dropped while others are moved to theendvalue. This is what I'm trying to achieve. - The
make_scopereturns a value which implementsScopetrait and storescontextby reference andendby value (that's why the trait is needed - it depends on type of the input value which determines type of theendvalue.) - While the returned
scopevalue exists, borrows dropped afterbeginwas called need to be accessed. This is what doesn't work for me.
What I tried
- First, I specified 'begin and 'end lifetimes as generic parameters of the Begin trait. Implementation then uses them to differentiate which references need to be dropped and which need to be transferred to the
end. - The compiler suggested to add the
+ use<'this, 'end, T>to the output type ofmake_scopemethod. However, when implemented, compiler then complains that the output also captures 'begin lifetime (which is undesired.) - When I expand (inline)
make_scope, everything starts working just fine. However, that's undesired as there's some more work in the function (unrelated to the issue, i.e. not in the example.) - Various other experiments which lead nowhere. E.g. try to specify output of
beginas both type alias andimpldeclaration with and without+ use<'end>.
What I'm trying to solve (the underlying goal)
I have a couple of begin/end functions. Begin functions tend to receive lots of input parameters and lots of them could use default values. But Rust doesn't support named arguments nor default parameters which makes using such functions difficult, therefore I encapsulate them with a struct which implements the Default trait.
Additionally, these functions need some common values/references which are stored in the Context struct (and which is also needed between begin/end calls.)
And finally, the begin function requires that end must be called later. (I use Drop trait for that - not included in the example for more explicit flow.)
Some of the parameters passed to begin are references to containers which are owned by a struct which needs to be used between begin and end and I don't want to copy them. That means they need to be borrowed for the duration of the begin call, but NOT between begin and end. I mean - they could be, they're not needed in that block, but that prevents calling mutable methods on the value which is really desired.
Playground Example
Here's the playground example..
- Does not compile, so you can see the error immediately.
- When the
let mut scope = context.make_scope(foo);line near the end of the file is commented and following block uncommented (which contains exactly same code as the method), everything works as desired.
use std::marker::PhantomData; // 'begin ... lifetime of references which need to live only until begin() call. // 'end ... lifetime of references which need to live until the end. trait Begin<'begin, 'end> { // Result of begin -> no longer capture 'begin, just 'end. type Out: End<'end>; // Begin uses 'begin references and then drops them. No longer accessing. // Returns new structure which can access only 'end references. fn begin(self) -> Self::Out; } trait End<'end> { fn end(&mut self); } struct Foo<'begin, 'end> { ref_begin: &'begin i32, ref_end: &'end i32, } struct FooEnd<'end> { ref_end: &'end i32, } impl<'begin, 'end> Begin<'begin, 'end> for Foo<'begin, 'end> { type Out = FooEnd<'end>; fn begin(self) -> Self::Out { // This is the last time ref_begin is supposed to be accessible. println!("Foo begin ({}, {})", self.ref_begin, self.ref_end); // From now on, ref_begin is supposed to be unborrowed while keeping reference to end. return FooEnd { ref_end: self.ref_end, }; } } impl<'end> End<'end> for FooEnd<'end> { // (Cannot consume self, because in some cases, this is called from drop...) fn end(&mut self) { println!("Foo end ({})", self.ref_end); } } // There might me multiple kinds of scope, the Begin and End is only one of them. trait Scope { fn finish(&mut self); } // Scope implementation for the End types. struct ScopeForEnd<'end, 'context, T: End<'end>> { end: T, context: &'context mut Context, _marker: PhantomData<&'end ()>, } impl<'end, 'context, T: End<'end>> Scope for ScopeForEnd<'end, 'context, T> { fn finish(&mut self) { let _ = self.context; self.end.end(); } } struct Context {} impl Context { // The idea is: // - Call begin() on the x and then drop 'begin references. // - Create Scope implementation which: // - Refernces self // - References 'end values. // - !!! DOES NOT REFERENCE 'begin VALUES !!! pub fn make_scope<'this, 'begin, 'end, T>(&'this mut self, x: T) -> impl Scope // The compiler suggest to write use<..> below, but when uncommented: // error[E0700]: hidden type for `impl foo::Scope` captures lifetime that does not appear in bounds // [...] // | ------ hidden type `ScopeForEnd<'end, 'this, <T as Begin<'begin, 'end>>::Out>` captures the lifetime `'begin` as defined here // + use<'this, 'end, T> where T: Begin<'begin, 'end>, { return ScopeForEnd { end: x.begin(), context: self, _marker: PhantomData, }; } } fn main() { let mut value_begin: i32 = 13; let value_end: i32 = 17; let mut context = Context {}; let foo = Foo { ref_begin: &value_begin, ref_end: &value_end, }; // Here, create Scope implementation. let mut scope = context.make_scope(foo); /* // When this is uncommented and replaces the scope above, // everything compiles just fine. let mut scope = ScopeForEnd { end: foo.begin(), context: &mut context, _marker: PhantomData, }; */ // Here, after Scope impl is created, the value_begin should be no longer borrowed. // Scope must exist. // Here's the error: // error[E0506]: cannot assign to `value_begin` because it is borrowed value_begin += 1; println!("Between begin and end ({})", value_begin); // After this point, scope is no longer needed. scope.finish(); let _ = value_end; let _ = context; } 4 posts - 3 participants

