Can `trait Foo<T>` have a method that returns `T`, `&T`, and `&mut T`? (Also a request for a code review of my plugin system prototype)

@kornel:

I mean that generic types are always maximally pessimistic and maximally inflexible about what can be done with them.

Types like &str can be shared, types like String can be moved, types like u8 can be copied. But a type T that could be any of them doesn't get these features. Quite the opposite. There's no room to say that if T is shareable, then share it, but if it's exclusive, then don't share it. It's always the worst most restrictive case, so you can't add memory-management flexibility with generics.

I think, I get it now. Thank you.

Rust has AsRef, AsMut and TryInto for these. So instead of a trait-for-getting-another-trait you should probably implement these directly? But I still don't understand what you're trying to do.

I believe, this wouldn't be possible, or wouldn't bring benefits (if I understand it correctly).

In a different thread I tried to explain the requirements and limitations. Does this make my intent more clear?

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).

It seems like you don't have any architecture for your plugins, and want to also make program's architecture abstract?

Yes, my plugin system should be a separate crate, that I can use in different other projects (and if I manage to create something useful, I'll probably publish the crate at some point).

Type of ownership and data sharing has to be non-abstract in Rust. Even when ownership is variable at run time (like Cow) or sharing is unclear (like RefCell), that possibility still has to be hardcoded.

So my thinking was, that the implementations of the UsePlugin<T> trait would represent hard-coded non-abstract types.

And indeed, this is working to a certain extent:

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

struct Struct {
    data: Option<String>,
}

impl<'a> Trait<'a, &'a str> for Struct {
    fn get(&'a mut self) -> &'a str {
        (&*self).data.as_ref().unwrap().as_str()
    }
}

impl<'a> Trait<'a, &'a String> for Struct {
    fn get(&'a mut self) -> &'a String {
        (&*self).data.as_ref().unwrap()
    }
}

impl<'a> Trait<'a, &'a mut String> for Struct {
    fn get(&'a mut self) -> &'a mut String {
        self.data.as_mut().unwrap()
    }
}

impl<'a> Trait<'a, Option<String>> for Struct {
    fn get(&'a mut self) -> Option<String> {
        self.data.take()
    }
}

fn main() {
    let mut s = Struct {
        data: Some(String::from("foo")),
    };

    // The following works, as long as only one line is uncommented:
    let a: &str = s.get();
    //let b: &String = s.get();
    //let c: &mut String = s.get();
    //let d: Option<String> = s.get();

    println!("{:?}", a);

    // Unfortunately, the following doesn't work, because there are two
    //  `&mut T` at the same time, even so I tried to "downgrade"
    // `&mut T` to `&T`:
    let a: &str = s.get();
    let b: &str = s.get();

    println!("{} {}", a, b);
}

Of course, this isn't a single method that returns different types, but different implementations of the same trait (my description in my first post wasn't great, I think).

Unfortunately, in the end, this isn't working, as I indicate at the end of the example.

This makes me fear, that my previous test with my previous implementation was incorrect, and will hit the same limitation...

On the other hand, after thinking about it more, using separate methods should work.

get_ref should work several times, if it takes &self, get_mut should work for a single concurrent &mut Plugin (hopefully, dropping this values makes it possible to get a different plugin. This wasn't possible with another experiment, where I used lots of lifetimes to get one step further). get_refcell also should work for several concurrent plugins, if it takes &self.

Oh, your "Plugin1 uses Plugin2" thing reminds me of ECS. See how Bevy does it. It can have an arbitrary number of components (your plugins?) and then has "systems" which can require access to any of these components.

I was considering ECSs. However, the benchmarks I saw, made me believe this wouldn't be efficient enough (adding components seems too slow). After thinking about it again, I might have misunderstood the benchmarks. I was thinking, adding components would be 'adding data' to be processed. But it might be actually 'adding plugins', which would be more than fast enough. Thanks for bringing ECSs to my attention again!