rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

Associated traits

Open comex opened this issue 8 years ago • 19 comments
trafficstars

RFC 1733 added trait aliases, like:

trait IntoIntIterator = IntoIterator<Item=i32>;

Well, why not allow putting those in traits, by analogy with associated types looking like type aliases? For example:

trait Handler {
    trait Arg;
    fn handle<ArgImpl: Self::Arg>(&self, arg: ArgImpl);
}

struct MyHandler;
impl Handler for MyHandler {
    trait Arg = IntoIterator<Item=i32>;
    fn handle<ArgImpl: Self::Arg>(&self, arg: ArgImpl) {
        for number in arg { println!("{}", number); }
    }
}

Example of a function that's generic over implementations of Handler:

fn example_generic_helper<HandlerImpl, ArgImpl>(handler: HandlerImpl, args: Vec<ArgImpl>)
    where HandlerImpl: Handler,
          ArgImpl: <HandlerImpl as Handler>::Arg {
    for arg in args {
        handler.handle(arg);
    }
}

Associated traits could also have supertrait bounds.

And if impl Trait syntax is extended to function arguments, they'd be a natural fit:

trait Handler {
    trait Arg;
    fn handle(&self, arg: impl Self::Arg);
}

(I was just writing some code where this could have come in handy.)

There's also the natural dual of allowing traits as generic parameters, just as associated types mirror regular type parameters and associated consts mirror the upcoming 'const generics'. Something like

fn foo<Impl, trait Trait> where Impl: Trait { … }

I think this has been proposed before.

comex avatar Oct 27 '17 00:10 comex

This could mix well with higher-kinded-types/#1598 (or whatever equivalent ends up getting implemented). Something like:

pub trait Map<K: Self::Key, V: Self::Value> {
    trait Key;
    trait Value;
    
    ...
}

impl<K: Hash, V> Map<K, V> for HashMap<K, V> {
    trait Key = Hash;
    trait Value = Any;
    
    ...
}

Ideally these would be more like 'associated type bounds'.

samsartor avatar Oct 30 '17 07:10 samsartor

The equivalent feature in GHC is called ConstraintKinds. (Probably already mentioned in one of the linked threads, but worth mentioning again.)

glaebhoerl avatar Oct 30 '17 14:10 glaebhoerl

Right now, traits can appear in type parameter position on traits, but presumably this only provides trait objects, not constraints. I suppose dyn https://github.com/rust-lang/rfcs/pull/2113 improves clarity but the trait remains necessary.

burdges avatar Nov 01 '17 10:11 burdges

I'm interested in collaboration on an RFC regarding this.

Centril avatar Dec 06 '17 01:12 Centril

Does it make sense to support this?

impl Handler for Foo {
    trait Arg = 'static;
}

kennytm avatar Jan 04 '18 20:01 kennytm

@kennytm What is the use case you have in mind? It feels natural to support lifetime bounds.

Centril avatar Jan 04 '18 21:01 Centril

@Centril something about this

trait PointerFamily {
    trait Bounds;
    type Pointer<T: ?Sized>;
}

struct ConstPtrFamily;
impl PointerFamily for ConstPtrFamily {
    trait Bounds = Copy + Ord + Hash + UnwindSafe + 'static;
    type Pointer<T: ?Sized> = *const T;
}

struct RefFamily<'a>(PhantomData<&'a ()>);
impl<'a> PointerFamily for RefFamily<'a> {
    trait Bounds = Copy + Send + Sync + UnwindSafe + 'a;
    type Pointer<T: ?Sized> = &'a T;
}

struct RcFamily;
impl PointerFamily for RcFamily {
    trait Bounds = Clone + UnwindSafe + 'static;
    type Pointer<T: ?Sized> = Rc<T>;
}

kennytm avatar Jan 04 '18 21:01 kennytm

cc @withoutboats thread: https://twitter.com/withoutboats/status/970117188274155522

glaebhoerl avatar Mar 04 '18 20:03 glaebhoerl

I've started a dedicated repo for research and writing on a ConstraintKinds RFC:

  • https://github.com/Centril/rfc-trait-parametric-polymorphism/

I'd love it if y'all would:

  • create issues with motivating examples
  • drop any ideas / complications as issues
  • collaborate -- if you want to help write the RFC, leave a note and I'll give you perms.

Centril avatar Apr 05 '18 21:04 Centril

I've been working on something, and Associated traits would work really nicely with it.

trait Comp {
	type Props: Clone + 'static;
	trait Events;

	fn new(props: Self::Props) -> Self;
}

struct Label {
	value: String,
}

impl Comp for Label {
	type Props = LabelProps;
	trait Events = LabelEvents;

	fn new(props: Self::Props) -> Self {
		unimplemented!()
	}
}

struct LabelProps {
	value: String,
}

trait LabelEvents {
}

impl LabelEvents for OnClick {
	/* ... */
}

struct OnClick;
struct KeyPress;

struct Context<C: Comp> {
}

impl<C> Context<C> {

	fn listen<E>(&mut self, func: impl for<'r> Fn(&'r mut Context<C>, E)) where E: C::Events {
		unimplemented!()
	}
}

fn create_panel() {
	let ctx: &mut Context<Label> = unimplemented!();

	ctx.listen::<OnClick>(|ctx, event| {
		// Do something with the event
	});

	// Compile error.
	ctx.listen::<KeyPress>(|ctx, event| {
	});
}

Jezza avatar Nov 27 '19 13:11 Jezza

This feature would come in very handy. My use case would be something like the following:

trait Monitor {
    type Action; // Usually some kind of enum
}

trait SetLikeMonitor : Monitor {
    fn mk_put() -> Self::Action; // these only exist in SetLikeMonitor!
    fn mk_rm() -> Self::Action;
}

struct SillySet { ... }

// SillySet is a set-like data structure, so we constrain what kind of monitors are allowed,
// because we need the `mk_put` and `mk_rm` functions to generate the appropriate actions.
impl MyTrait for SillySet {
    trait Mon: SetLikeMonitor;

    fn apply<M: Self::Mon>(...) -> Self::Mon::Action {
        ...
        M::mk_put()
    }
}

So SillySet can declare that it is a set-like data structure, and thus apply can return an action which can be put or rm, actions appropriate for sets. Then, using apply, you can either call it with apply::<()>() to disable monitoring, and due to monomorphisation the monitoring overhead will get optimised out, or call apply::<SomeSetLikeMonitor>() to still get the monitoring.

(Edit: Someone on the Rust community discord came up with a workaround)

Kiiyya avatar Sep 01 '21 09:09 Kiiyya

Several days ago I figured that that would help me implement/request trait for the associated type (through a few more steps) https://stackoverflow.com/questions/71522871/rust-implement-trait-for-the-associated-type

There's a pattern that allows to achieve what I wanted in Rust with the addition of generic associated types (GATs). Yet I'm still working on pinpointing the pattern. Warning: I haven't used Haskell for quite a while and I didn't work much with its kind system.

JohnScience avatar Mar 20 '22 04:03 JohnScience

One important application of associated traits is ability to define DefaultSpecializationExt trait like this:

trait DefaultSpecializationExt {
    trait Trait;
    type DefaultSpecialization: Self::Trait; 
}

Without associated traits, the default specialization would be unbounded or there would be a whole family of traits related only intuitively.

DefaultSpecializationExt can be implemented for generic types which have a canonical set of generic parameters. One example of such is Span<LineColumn, Offset>. LineColumn by default can be (usize,usize) and Offset can be usize.

JohnScience avatar Mar 29 '22 21:03 JohnScience

I could also really use this, as I have

pub trait Item  { ... }
impl Item for EnvironmentItem { ... }
impl Item for PanelItem { ... }

pub struct ItemUI<I: Item, T: Send + Sync + 'static> {
    ...
}

where I want

ItemUI<PanelItem, T: PanelItemHandler>

and

ItemUI<EnvironmentItem, T: Any>

to be enforced. I could easily do that with

pub trait Item {
  trait Handler;
  ...
}

impl Item for EnvironmentItem {
  trait Handler = Any;
  ...
}

impl Item for PanelItem {
  trait Handler = PanelItemHandler
  ...
}
 
pub struct ItemUI<I: Item, T: I::Handler + Send + Sync + 'static> {
    node: Arc<Node>,
    items: Mutex<FxHashMap<String, HandlerWrapper<I, T>>>,
}

but other methods would require a bigger hassle, such as making multiple ItemUI structs with those trait bounds in place using a macro.

technobaboo avatar Sep 14 '22 17:09 technobaboo

What are the next steps here? Does someone need to write a formal RFC?

shlevy avatar Feb 18 '24 02:02 shlevy