"Lifetime parameters or bounds on method `execute` do not match the trait declaration" – but why?

I encountered a problem with lifetimes that I don't get. Either I'm blind, and I'm missing a spelling error, or I'm missing something about lifetimes...

This code (Playground):


trait Plugin {
    fn execute<'a, Sys>(&mut self, sys: &'a mut Sys);
}

trait UsePlugin<'a, T> {
    fn use_plugin(&'a mut self) -> T;
}


struct Plugin1 {
    data: String,
}


struct Plugin2 {
    data: String,
}


impl Plugin for Plugin2 {
    fn execute<'a, Sys>(&mut self, sys: &'a mut Sys)
    where
        Sys: UsePlugin<'a, &'a mut Plugin1>,
    {

    }
}

fn main(){}

...results in:

error[E0195]: lifetime parameters or bounds on method `execute` do not match the trait declaration
  --> src/main.rs:22:15
   |
3  |     fn execute<'a, Sys>(&mut self, sys: &'a mut Sys);
   |               --------- lifetimes in impl do not match this method in trait
...
22 |     fn execute<'a, Sys>(&mut self, sys: &'a mut Sys)
   |               ^^^^^^^^^ lifetimes do not match method in trait

If I remove Sys: UsePlugin<'a, &'a mut Plugin1>, the problem goes away – but why? Why doesn't this work?

The trait (Plugin) requires that you implement a method execute that's generic over any 'a and Sys. When implementing the trait, you can't impose additional bounds of your own on the method, you have to "satisfy the contract" -- supply a method that's generic over any 'a and Sys, a generic lifetime and type that the caller of the function will determine. That way consumers of the trait can rely on consistent behaviour. If it was possible to only partially implement a trait, it couldn't be relied on.

I don't understand from your example what you're ultimately trying to accomplish, so I'm not sure what to suggest as an alternative or way forward.

1 Like

Thank you, @quinedot!

Basically, what I try to archive is, that a plugin can not request access to itself (statically checked).

Before, I had a struct PluginSystem which implements UsePlugin for all plugins.

But this way, a plugin could request access to itself.

My solution was to create a NewType of PluginSystem for every plugin. This way I can implement UsePlugin on this NewType for all plugins but the active plugin.

The problem is, that the NewType wraps &mut PluginSystem, which lead to the requirement of the problematic lifetime above.

What I try to archive (my plugin system) is very complex for me, so my explanation probably isn't easy to understand. And I think my full prototype is too difficult to understand, to post it in full and expect help.

However, I have a few ideas of alternatives that I will try.

The trait ( Plugin ) requires that you implement a method execute that's generic over any 'a and Sys . When implementing the trait, you can't impose additional bounds of your own on the method, you have to "satisfy the contract"

In my previous solution (without the lifetime) this worked however (at this time the generic Sys was on the trait itself, not on the method execute, however. Because of the new lifetime I had to move the generic to the method).

Negative trait bounds aren't yet directly supported; there's a lot of details to work out, interaction with specialization, and so on. However, there is one form of negative reasoning built in to the trait system already: No conflicting implementations allowed.

So it's somewhat indirect, but you can use this to set up a situation that is, if I understand correctly, something like what you're trying to achieve.

I don't know if this will nudge things in the right direction, but you can use a generic lifetime on the trait itself. You could also use single-method traits like this one and group them together using a supertrait.

Yesterday I started a second thread in regard to what I try to achieve in the end, which also contains a minimal example of my working solution (which still has the problem I try to overcome).

However, it seems, this problem can't be solved, currently.

However, there is one form of negative reasoning built in to the trait system already: No conflicting implementations allowed.

Thank you! This is great example, and I bookmarked it for when I need this kind of permission handling.

I considered a sealed trait at the beginning, but I think this can't be used here.

Basically, we have this:

  • PluginSys, which holds all plugins (which hold their data)
  • Plugins have an execute method, which takes &mut PluginSys as argument
  • UsePlugin<PluginN> is implemented for PluginSystem, so plugins can use UsePlugin::get_plugin_mut to get other plugins
  • This indirection is required because PluginSystem is generated by a macro, so plugins don't know anything about PluginSystem, or the other plugins it contains (they only know the types of the plugins they depend on, which they can access by requiring UsePlugin<T> on PluginSys)
  • UsePlugin<T> needs to be implemented for all plugins, so it's not possible to forbid UsePlugin<Plugin1> when PluginSys executes Plugin1 (if I'm not missing anything)

My solution was to create a NewType Plugin1Sys for Plugin1, and then implement UsePlugin<T> on Plugin1Sys for all plugins but Plugin1.

PluginSys would then pass Plugin1Sys<&mut PluginSys> to Plugin1::execute (which introduced the problematic lifetime this thread initially was about).

Would there be a benefit of using your approach on Plugin1Sys?

Maybe I could use this to prevent the end-user (which has full access to the code the macro generates in the end-users crate) to implement UsePlugin<Plugin1> for Plugin1Sys.

Thank you, @Rustaceous!

you can use a generic lifetime on the trait itself

Wouldn't that mean, that this lifetime is as long as the plugin exists?

Because the PluginNSys NewTypes only exist as long as the call to Plugin::execute lasts.

You could also use single-method traits like this one and group them together using a supertrait.

I think you Playground refers to your first tip, and I'm not very sure how I'd use your second tip in this context. With the above information, do you still think, this could work here?

I don't think so. This is what the Rust Reference says about generic arguments.

Functions, type aliases, structs, enumerations, unions, traits, and implementations may be parameterized by types, constants, and lifetimes. These parameters are listed in angle brackets ( <...> ), usually immediately after the name of the item and before its definition.

Generic parameters are in scope within the item definition where they are declared. They are not in scope for items declared within the body of a function as described in item declarations.

There's no further information about traits specifically.

There is no mention of an implicit requirement that the implementing type outlive the reference. From what I can tell it is purely a matter of where they are in scope, the whole trait or just a function. Obviously using generics for functions is cleaner because the compiler can use lifetime elision, but trait generics are going to be necessary sometimes.

If I'm right, relationships between lifetimes are defined only by lifetime bounds.

  • where T: 'a
  • where Self: 'a
  • where 'a: 'b

There are people here who understand the language and the compiler much better than I so I would not be surprised if I've missed something.

Supertraits, on the other hand, are more straightforward. It's just a shorthand for where Self: T. When a single trait has a lot of generic arguments it looks pretty nasty, so I like to group them under a supertrait. That way smaller traits are written with just the generics that they need. You can also use the supertrait as a generic type bound without listing all of the dependent traits. Example with an auto-impl for the supertrait.

@Rustaceous: Thanks again! This makes sense, I will give both a try.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.