Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Allow external #[deriving(...)] implementations #11813

Closed
sfackler opened this issue Jan 26, 2014 · 9 comments
Closed

RFC: Allow external #[deriving(...)] implementations #11813

sfackler opened this issue Jan 26, 2014 · 9 comments
Labels
A-syntaxext Area: Syntax extensions

Comments

@sfackler
Copy link
Member

Background

Now that #11151 has landed, third-party code can define syntax extensions just as the compiler does - with the exception of adding new types to #[deriving(..)]. It is possible to write an extension that does the same thing as #[deriving(..)] does, but it has to be implemented as #[deriving_MyTrait] instead of #[deriving(MyTrait)]. This unfortunate, especially when a user wants to derive multiple first- and third-party traits:

#[deriving(Clone, Eq, Ord)]
#[deriving_Foo]
#[deriving_Bar]
#[deriving_Baz]
pub struct Blah {
    ...
}

The repetition of multiple #[deriving_Foo]s was the driving force for the creation of #[deriving(..)] in the first place!

The Proposal

Change the signature of the macro registration function to this:

pub type MacroCrateRegistrationFun =
    fn(macro_callback: |Name, SyntaxExtension|,
       deriving_callback: |~str, DerivingExtension|);

#[deriving(..)] types will be registered by the extension infrastructure just like other syntax extensions are.

This does have a (potentially) large downside: the extension infrastructure will have to have special knowledge of #[deriving(..)] to keep track of the registered extensions. I think it's probably worth it, and there are already some other "special" extensions like trace_macros!. What does everyone else think?

@huonw
Copy link
Member

huonw commented Jan 26, 2014

I think we need a namespacing solution. One possibility:

extern mod some_crate;

#[deriving(some_crate::Trait)]
struct Foo { ... }

but this would mean that Trait may have a different path in #[deriving] to its real path.

@alexcrichton
Copy link
Member

Another possibility would be to have a MacroRegistrar struct and then the macro registration function takes a mutable version of the struct. We're then free to add methods in the future without breaking existing code. I do think that the "most right" solution would be deriving(MyTrait) to follow the same syntax as the existing derivings.

If we wanted to resolve different deriving implementations, I can imagine things getting tricky fairly quickly, but it would certainly solve the namespace problem!

@Kimundi
Copy link
Member

Kimundi commented Jan 26, 2014

Instead of hardcoding to deriving, would it make sense to introduce the concept of an extendable attribute in general? Eg, if you have something like #[foo(a, b, c, ..)], a syntax extension could either register itself as the sole handler of all arguments for the attribute foo, or it could register itself as a handler for one argument of foo.

The whole deriving infrastructure could then just switch to providing implementations for a argument to #[deriving(...)], and additional user implementations would "just work".

@sfackler
Copy link
Member Author

I think we need a namespacing solution for macros in general. I'm not totally sure how it should work, especially in relation to use statements. I'll write up some thoughts in another RFC.

@kmcallister
Copy link
Contributor

I'm interested in this. Here's a possible design:

mylib.rs:

pub trait MyTrait { ... }

mylib_plugin.rs:

extern crate mylib;

use mylib::MyTrait;

// signaure is like the functions in src/libsyntax/ext/deriving
fn expand_deriving_mytrait(cx: &mut ExtCtxt,
                           span: Span,
                           mitem: Gc<MetaItem>,
                           item: Gc<Item>,
                           push: |Gc<Item>|) {
    ...
}

#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
    reg.register_deriving(quote_full_path!(MyTrait), expand_deriving_mytrait);
}

user.rs:

extern crate mylib;

#[phase(plugin)]
extern crate mylib_plugin;

use mylib::MyTrait;

#[deriving(MyTrait)]
struct Foo;

The quote_full_path! macro does name resolution and expands to the full path, e.g. ::mylib::MyTrait, possibly along with other information to ensure that the MyTrait present when the plugin is built is the same one present when the plugin is used.

The implementation of deriving will likewise resolve its arguments to trait definitions, instead of checking a fixed set of strings.

What do people think?

@sfackler
Copy link
Member Author

Adding support for name resolution to expansion would be awesome, but very nontrivial. The internals for that will definitely need to be thought through.

@Manishearth
Copy link
Member

@kmcallister Is it necessary to provide a full function like the ones from ext::deriving?

Almost all of these are of the form that there are one or two functions (let's call it f). The deriving decorator takes the return values of <member>.f for all members, does something with them, and produces a return value for <parent>.f. Perhaps we could find a way to reduce boilerplate with this in mind? There already is a lot of similar code in ext::deriving, and it feels a bit strange that the procedure for creating a new one is to copy-paste and edit the types a bit.


Offshoot idea ahead, this is just something I realized could be done if we manage to do the above.

Reducing boilerplate might even pave the way for easy-deriving generators:

#[auto_derive]
trait foo {
 #[derive_scheme(EncodableScheme)]
 fn bar(x: uint); // Will just call bar() on all member data with the same parameter
 #[derive_scheme(CloneScheme)]
 fn baz() -> Self; // Will call baz() on all member data, and create a new object out of those
}

Now we only need to define the scheme by which the return values of the method for the members get treed up to create the return value for the method called on the parent.

For example, one can define

trait Incrementable {
 #[derive_scheme(CloneScheme)]
 fn increment(&self) -> Self; // When incrementing, all member data is incremented, and an object is made from that data
}

where we define how to increment ints and a few other objects, and then objects containing solely Incrementable ones can be incremented too.

The plugin writer can define new schemes too -- which is something that probably is rather rare given the variety of deriving schemes which we already use.

@Manishearth
Copy link
Member

The above proposal has been pre-rfc'd here

@rust-highfive
Copy link
Collaborator

This issue has been moved to the RFCs repo: rust-lang/rfcs#480

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-syntaxext Area: Syntax extensions
Projects
None yet
Development

No branches or pull requests

7 participants