0

I need a trait that allows me to construct a object that borrows an object that borrows something. In the following example that is PaperBin. https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=78fb3f88b71bc226614912001ceca65b

trait GarbageBin<'a,'b>{
    fn new(rubbish: &'b Paper<'a>) -> Self;
}
struct PaperBin<'a,'b> {
    rubbish: &'b Paper<'a>
}
struct Paper<'a> {
    matter: &'a [u8]
}
impl<'a,'b> GarbageBin<'a,'b> for PaperBin<'a,'b> {
    fn new(rubbish: &'b Paper<'a>) -> Self{
        Self {
            rubbish: rubbish
        }
    }
}
fn create_bin_with_rubbish<'a,'b, T>()
    where T: GarbageBin<'a,'b>
{
    let matter = &[1][..];
    let rubbish = Paper{matter};
    This gives an error:
    //let garbage_bin = T::new(&rubbish);
}

#[test]
fn run() {
    create_bin_with_rubbish::<PaperBin>();
}

I need to create GarbageBins generically determined on a function call, as in the example. This example looks maybe a unnecessary regarding the fixed type Paper in the new() associated function of the Trait. This should become a Trait object in the real code.

How can I realize the creation of garbage_bin the generic type T?

here is the error message that I get, if I un-comment the line:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'a` due to conflicting requirements
  --> src\reference_to_reference\mod.rs:23:23
   |
23 |     let garbage_bin = T::new(&rubbish);
   |                       ^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'a` as defined on the function body at 18:28...
  --> src\reference_to_reference\mod.rs:18:28
   |
18 | fn create_bin_with_rubbish<'a,'b, T>()
   |                            ^^
note: ...but the lifetime must also be valid for the lifetime `'b` as defined on the function body at 18:31...
  --> src\reference_to_reference\mod.rs:18:31
   |
18 | fn create_bin_with_rubbish<'a,'b, T>()
   |                               ^^
note: ...so that the types are compatible
  --> src\reference_to_reference\mod.rs:23:23
   |
23 |     let garbage_bin = T::new(&rubbish);
   |                       ^^^^^^
   = note: expected `GarbageBin<'_, '_>`
              found `GarbageBin<'a, 'b>`
eggyal
  • 122,705
  • 18
  • 212
  • 237
  • One wonders whether `Paper` and `PaperBin` should not *own* `matter` and `rubbish` respectively, rather than only borrowing them? – eggyal Jun 14 '21 at 18:08
  • Paper and PaperBin shall not own the data (then there would be not much of a problem here). `matter` and `rubbish` do not need to survive the function call and neither does the garbage_bin need to survive the function all. Every object shall be dropped within the functions body scope. The function shall in practice get some referenced data from somewhere and the GarbageBin shall send the `rubbish` somewhere via a communication channel. – Robert Kalinke Jun 14 '21 at 18:27
  • @eggyal Thanks you! I tried to make the requirements more clear in the edit. Probably one should not construct the GarbageBin at all, and rather use a free function or add the `rubbish` argument to the flush method. The latter unfortunately close to impossible in the current architecture. – Robert Kalinke Jun 14 '21 at 19:00
  • I rolled-back your edit because your explanation in the comments was sufficient to produce the answer, and the edits distracted from the core issue (and also aren't incorporated in my answer). – eggyal Jun 16 '21 at 08:14

1 Answers1

2

Your immediate issue is that T: GarbageBin<'a, 'b> where 'a and 'b are parameters of create_bin_with_rubbish and must therefore outlive calls to that function—but the actual lifetimes passed to T::new are only internal to the function and do not therefore satisfy those bounds.

Instead of parameterising create_bin_with_rubbish with lifetimes 'a and 'b, one way to resolve this would be to use instead an HRTB (higher-ranked trait bound):

fn create_bin_with_rubbish<T>()
where
    T: for<'a, 'b> GarbageBin<'a, 'b>,

However, PaperBin implicitly requires that 'a: 'b, and such bounds are not currently supported in HRTBs. But this in itself is indicative of the second lifetime parameter being unnecessary: remember, a lifetime parameter only means "lives at least as long as..." and multiple instances of the same parameter can represent different concrete lifetimes (so long as they each satisfy that same bound). So let's simplify:

struct PaperBin<'a> {
    rubbish: &'a Paper<'a>,
}

Finally, using the HRTB does require breaking the immediate link between the lifetimes of GarbageBin and its implementor PaperBin—but that makes sense, because you're trying to pass in the PaperBin type without knowing its lifetime parameters and are then invoking GarbageBin's constructor method upon it to obtain a PaperBin with specific lifetime parameters. So now we implement GarbageBin<'a> for PaperBin<'_>—but we still need the constructor to return an instance of PaperBin<'a>, for which we add an associated type:

trait GarbageBin<'a> {
    type Constructed: GarbageBin<'a>;
    fn new(rubbish: &'a Paper<'a>) -> Self::Constructed;
}

struct Paper<'a> {
    matter: &'a [u8]
}

struct PaperBin<'a> {
    rubbish: &'a Paper<'a>
}

impl<'a> GarbageBin<'a> for PaperBin<'_> {
    type Constructed = PaperBin<'a>;
    fn new(rubbish: &'a Paper<'a>) -> PaperBin<'a> {
        PaperBin { rubbish }
    }
}

fn create_bin_with_rubbish<T>()
where
    T: for<'a> GarbageBin<'a>,
{
    let matter = &[1][..];
    let rubbish = Paper { matter };
    let garbage_bin = T::new(&rubbish);
}

fn main() {
    create_bin_with_rubbish::<PaperBin>();
}

See it on the Playground.

Using a GAT (generic associated type), which is currently an unstable feature, you can push the lifetime parameter from the trait into the associated type, which negates the need for an HRTB:

#![feature(generic_associated_types)]

trait GarbageBin {
    type Constructed<'a>: GarbageBin;
    fn new<'a>(rubbish: &'a Paper<'a>) -> Self::Constructed<'a>;
}

struct Paper<'a> {
    matter: &'a [u8]
}

struct PaperBin<'a> {
    rubbish: &'a Paper<'a>
}

impl GarbageBin for PaperBin<'_> {
    type Constructed<'a> = PaperBin<'a>;
    fn new<'a>(rubbish: &'a Paper<'a>) -> PaperBin<'a> {
        PaperBin { rubbish }
    }
}

fn create_bin_with_rubbish<T: GarbageBin>() {
    let matter = &[1][..];
    let rubbish = Paper { matter };
    let garbage_bin = T::new(&rubbish);
}

fn main() {
    create_bin_with_rubbish::<PaperBin>();
}

See it on the Playground

eggyal
  • 122,705
  • 18
  • 212
  • 237
  • Impressive. Thanks! With the two lifetimes it would be impossible to add the `'a:'b` bound to the Higher-Ranked-Trait-Bound in the where clause of the `create_bin_with_rubbish` function, right? Like this is not realizable at all (the bound inside the "for"): `where T: GarbageBin + for<'a:'b,'b> GarbageBinConstructor<'a,'b>` – Robert Kalinke Jun 15 '21 at 06:46
  • @RobertKalinke: Correct, that's not currently possible. Also please note I realised, for the simple example given, you don't actually need to create a separate trait—simpler solution given in revised answer above. – eggyal Jun 15 '21 at 06:47
  • @RobertKalinke: If the lifetimes need to be kept distinct for some other reason, `create_bin_with_rubbish` can [use a single HRTB for each parameter](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e5f37145c2435ba5a40366850d68f0ef), since that is satisfied by its implementation. If there is a requirement for a more complex lifetime relationship, perhaps that is best asked as another question? – eggyal Jun 15 '21 at 08:09