Rust 1.15 brings custom derive to stable libraries
Libraries can generate trait implementations on stable Rust through custom derive. The initial procedural-macro interface is deliberately limited, with parsing and code generation handled by separate crates.
A serialization library can work on stable Rust while still making its users prefer nightly. The missing piece is often the code that connects an ordinary structure to the traits defined by the library. Writing that implementation by hand repeats information already present in the type definition. Custom derive in Rust 1.15 makes the generated implementation available through stable syntax and a stable compiler.
The February 2 announcement names Serde and Diesel as prominent examples. Both libraries already worked on stable, but their more convenient interfaces relied on custom derive. The release addresses that source of nightly dependence. It does not claim that every procedural macro capability has become stable at once.
I find Rust attractive where a program needs the control usually associated with C and stronger guarantees about how values are used. Library ergonomics matter to that choice. A safety model is easier to apply consistently when routine integration code can be generated from a visible type definition.
Deriving a trait is existing behavior with a new author
Rust already allows selected standard-library traits to be implemented through #[derive(...)]. The announcement uses Debug on a structure with a string field. The compiler produces the implementation that permits formatted inspection of the structure without requiring the programmer to repeat the field handling manually.
Custom derive lets a library participate in the same kind of source-level request. The programmer lists a derive name on a type, and the macro generates the corresponding implementation. Generated output is still Rust code implementing traits. The change concerns who can supply the generation logic and how a stable compiler invokes it.
The release demonstrates Serde with the 0.9 versions of serde, serde_derive, and serde_json. Those versions belong to the example as published today. Substituting configuration remembered from another version would obscure what the stable feature actually makes possible in this release.
[dependencies]
serde = "0.9"
serde_derive = "0.9"
serde_json = "0.9"
The accompanying Rust source imports the derive crate with the macro-use attribute and names the desired implementations on the structure. This is the contemporary import style used in the announcement.
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
#[derive(Serialize, Deserialize, Debug)]
struct Record {
name: String,
}
These declarations describe a type whose serialization support can come from the derive implementation. The library still defines the traits and the conversion functions. Derive connects the type to those contracts using its fields, rather than introducing a separate runtime reflection system.
The stable interface begins with custom derive
The book chapter in the Rust 1.15 source states the limit directly. Procedural macros are currently available for custom derive. It discusses broader possibilities as future work, so those possibilities should not be presented as features delivered today.
The chapter also requires the procedural macro to live in its own crate. Its example follows the convention of naming a derive crate after the library with a derive suffix. The consumer adds a dependency on that crate, and the macro crate declares its library kind in Cargo configuration.
[lib]
proc-macro = true
This is a build-time separation. The compiler loads the procedural macro to process the annotated type and obtain generated code. Application code consumes the resulting implementation. The organization makes it possible to distinguish code that runs during compilation from the application code it produces.
A macro author uses #[proc_macro_derive(Name)] on an exported function whose input and output are TokenStream. The derive name is what the consuming type lists in its attribute. The chapter then shows the function converting its input to a string and parsing it before constructing the implementation.
Parsing is outside the compiler interface
The initial TokenStream interface is intentionally small. The versioned chapter says its current useful operation is conversion to a string, and describes a richer interface as future work. Macro authors therefore need another representation if they want to inspect the structure of a type definition.
The chapter uses syn to parse that string and quote to produce Rust code. Its example pins syn to 0.10.5 and quote to 0.3.10. The point of those dependencies is concrete. Parsing Rust syntax is substantial work, and a code-generation template is easier to maintain than manually concatenating every token.
The parsed representation exposes the identifier of the annotated type. A quote template can insert that identifier into a generated impl block. The resulting tokens are converted back into the form returned to the compiler, which incorporates the implementation into the program being compiled.
This division lets the compiler stabilize a limited extension mechanism while libraries provide more convenient syntax handling. It also means macro authors depend on the behavior of parsing and generation crates. The small compiler interface does not eliminate that engineering work, but it gives the ecosystem a shared place to do it.
The book example generates a method that prints a type name. A serialization derive has more work because it must inspect fields and generate the appropriate trait implementation. The small example demonstrates the compile-time flow without suggesting that a useful serializer can ignore the structure or types of those fields.
Generated code still needs a contract
A derive invocation can save repeated implementation code without specifying every semantic choice made by the library. The programmer still needs to understand the trait being derived and the data representation it exposes. Generation makes the implementation regular, but regularity alone does not decide whether a representation is suitable for an application protocol.
That is why the release examples retain ordinary library calls for serialization and deserialization. The generated traits participate in a larger API. An application still chooses when to encode a value, where to send the resulting data, and how to handle a decoding failure. Custom derive automates the field-level implementation rather than those operational decisions.
Diagnostics are another part of the contract. The versioned book says a procedural macro reports an error by panicking with a message. Its introductory example uses unwraps for simplicity. That is an explanation of the mechanism, not an endorsement of unexplained failures in the user experience of a production library.
A useful derive should make an unsupported input understandable at compile time. The compiler can identify a failure during expansion, while the macro author knows which restriction was violated. Even with a stable mechanism, the quality of the error remains partly a library design responsibility.
I like the fact that the resulting behavior remains expressed through traits. The generated implementation must fit the same interface that a handwritten implementation would use. A reader can understand the application in terms of that interface, then inspect the derive when the generated details matter.
Stabilization follows a common use case
The announcement traces this work to RFC 1681, opened in July of the previous year and merged in August. Development and testing then led to stabilization in this release. The narrow scope is significant because the motivating libraries had an immediate need that did not require finishing every possible macro facility.
The project calls the feature macros 1.1 and notes that it provides infrastructure for further work. That name should not obscure the practical delivered change. A library can now ask stable users to derive its traits through a compiler-supported mechanism, reducing the need to choose nightly just for that integration.
This affects library adoption separately from language expressiveness. A program might already be possible with handwritten implementations or another code-generation path. Stable custom derive makes the convenient path compatible with a stable toolchain, which changes the cost of maintaining and distributing the program.
Build scripts have a separate compatibility correction
The release also changes when Cargo build scripts obtain OUT_DIR. The announcement says reading it with env! while compiling the build script was a bug, particularly for cross-compilation. Build scripts should read the variable at execution time through std::env instead.
That distinction matters because code generation now has several visible places in the workflow. A build script has its own execution environment, while a derive macro receives source tokens through the compiler. Correctly locating the required input is part of making either form of generation reliable. Rust 1.15 stabilizes the derive route and corrects a build-script assumption whose timing could otherwise select the wrong output directory.
The release also makes the Rust build system written in Rust and Cargo the default. The announcement describes it as a long-running transition already exercised on the development branch. This is another use of the ordinary package tooling in producing the language itself, with a different role from the procedural macro crates consumed by application builds.