I am trying to force the user to use a type specified during construction as an argument for &mut self method. The method takes the argument by reference, not value, so the nested values shouldn't be borrowed by the object. It seems that if the method has explicit generic type from struct it borrows nested value but if it has method defined generic type - it is ok (or wrong when the new generic type is limited to the struct generic type).
I am looking for some Rust rules for this behavior, but I can't find anything in documentation. Do you have any explanation for this behavior? Is there any other way to force using the type specified during construction?
This is my code snippet:
use std::marker::PhantomData;
struct A<T> { _t: PhantomData<T> }
impl<T> A<T> {
fn new() -> A<T> { Self { _t: PhantomData, } }
fn dummy(&self) { }
fn handle_a(&mut self, _t: &T) -> Option<()> { None }
fn handle_b<TT>(&mut self, _t: &TT) -> Option<()> { None }
fn handle_c<TT: Same<T>>(&mut self, _t: &TT) -> Option<()> { None }
}
pub fn main() {
struct B<'a>(&'a [u8]);
let mut a: A<B<'_>> = A::new();
let data = vec![3; 10];
let b = B(&data);
a.handle_a(&b);
drop(b);
drop(data);
a.dummy();
let data = vec![3; 10];
let b = B(&data);
a.handle_b(&b);
drop(b);
drop(data);
a.dummy();
let data = vec![3; 10];
let b = B(&data);
a.handle_c(&b);
drop(b);
drop(data);
a.dummy();
}
pub trait Same<T> {}
impl<T> Same<T> for T {}
And this is the compiler output:
error[E0505]: cannot move out of `data` because it is borrowed
--> src/main.rs:20:10
|
16 | let data = vec![3; 10];
| ---- binding `data` declared here
17 | let b = B(&data);
| ----- borrow of `data` occurs here
...
20 | drop(data);
| ^^^^ move out of `data` occurs here
21 | a.dummy();
| - borrow later used here
|
help: consider cloning the value if the performance cost is acceptable
|
17 | let b = B(&data.clone());
| ++++++++
error[E0505]: cannot move out of `data` because it is borrowed
--> src/main.rs:34:10
|
30 | let data = vec![3; 10];
| ---- binding `data` declared here
31 | let b = B(&data);
| ----- borrow of `data` occurs here
...
34 | drop(data);
| ^^^^ move out of `data` occurs here
35 | a.dummy();
| - borrow later used here
|
help: consider cloning the value if the performance cost is acceptable
|
31 | let b = B(&data.clone());
| ++++++++
For more information about this error, try `rustc --explain E0505`.
You're right that B<'x> and B<'y> are different types. And your handle_x works for the defined B<'a> type. But handle_x works only for specific set of possible types and it forces user to provide an implementation of Alt trait for his custom type. I'm looking for more general solution - where the user could use Copy types, B<'a, 'b> types, enums, etc. For this reason I don't understand why handle_a borrows nested type from generic but handle_b doesn't borrow this nested type. The difference seems to be only that handle_a uses a struct defined generic type and handle_b uses a method defined generic type.
The problem could be visible with standard Option:
fn handle_o<T>(_o: &mut Option<T>, _t: &T) { }
pub fn main() {
struct B<'a>(&'a [u8]);
let mut o: Option<B<'_>> = None;
let data = vec![3; 10];
// ---- binding `data` declared here
let b = B(&data);
// ----- borrow of `data` occurs here
handle_o(&mut o, &b);
drop(b);
drop(data);
// ^^^^ move out of `data` occurs here
assert!(o.is_none());
// - borrow later used here
}
Borrows live until end of scope. (Non lexical lifetimes allow some reduction.) Your handle_o is making the variables have the same type with same implied lifetime. The basic rules[1][2] can't be broken but rust is allowed to be stricter.
In terms of the OP struct A<T>. The structure is always tied to implicit lifetime of type parameter.
The first causes lifetimes to be the same or otherwise related in a way that keeps the borrow in the arg (type T) alive when A<T> is used. The second introduces a new type parameter (type TT) that doesn't cause lifetimes (or the generic types more generally) to be related.
Let's walk through it.
impl<T> A<T> {
// This takes `self: &mut A<T>` and `&T`
// When `T = B<'x>`, that's `self: &mut B<'x>` and `&B<'x>`.
fn handle_a(&mut self, _t: &T) -> Option<()> { None }
}
// Let's say this is `A<B<'a>>`.
let mut a: A<B<'_>> = A::new();
let data = vec![3; 10];
// Call this `B<'b>`. Uses of `b` will keep `data` borrowed.
let b = B(&data);
// `&mut X` is invariant in `X`, so in terms of the `impl` above,
// we must have `T = B<'a>` exactly. `&B<'b>` can coerce to `&B<'a>`
// so long as `'b: 'a`. This means that uses of `'a` will keep `'b`
// active (which will keep `data` borrowed).
a.handle_a(&b);
// (`B<'_>` has no drop glue so this only adds uses of borrows,
// it doesn't move or remove any.)
drop(b);
// The code cited in the error.
drop(data);
// This is a use of `'a`, which keeps `'b` active, which keeps `data`
// borrowed. Being borrowed while attempting a move with `drop(data)`
// results in a borrow checker error.
a.dummy();
Unfortunately there is no "generic type constructors" or higher-kinded types in Rust, where you could conceptually do...
struct A<T<..>> { _t: PhantomData<T> }
impl<..Generics, T<..>> A<T<..Generics>> {
// Same type constructor, different generics, doesn't impose
// type or lifetime equality or other constraints
// vvvvv
fn handle_d(&mut self, _t: &T<..>) {}
}
...much less something more general to handle "that or Copy or ..." in the type system.
Maybe you could say more about the motivation. Is this for a trait or such? (The only user in the snippet is the author.) Asking in part because in the general case, borrows can definitely be transferred with an API like that in the examples.
Oh it really is almost exactly like you shared, and consumers just get to choose the T, heh. Failure of imagination on my part.
Unfortunately I don't think there's a workaround purely on the function signature level.[1] From a type system perspective the lifetime is "flowing into" handle.