-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Add Builder guidelines #1036
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
Add Builder guidelines #1036
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
- Start Date: 2015-04-06 | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
|
||
Provide guidelines for builder types in the standard library. | ||
|
||
# Motivation | ||
|
||
We use builder pattern in a few places in the standard library, but they are not consistent. We | ||
have `thread::Builder`, `process::Command` and there’s `fs::OpenOptions`. They are somewhat ad-hoc | ||
and inconsistency makes forming the intuition about them hard. | ||
|
||
A common example is people not knowing about `OpenOptions` builder and being confused by its name | ||
and lack of discoverability. | ||
|
||
# Detailed design | ||
|
||
## Guidelines | ||
|
||
### Naming | ||
|
||
These guidelines suggest a few naming schemes, depending on situation at hand: | ||
|
||
* If the module, which exports the builder, is expected to only export a single builder, even in | ||
the future, and the module itself has a descriptive name, calling the builder “Builder” is | ||
recommended. For example: | ||
|
||
* `thread::Builder` builds and spawns threads; | ||
* `process::Builder` builds and spawns processes; | ||
|
||
but *not* `fs::Builder`, which opens files; | ||
* If the builder produces one type of objects, use the name of buildee suffixed with “Builder”. | ||
This scheme has an advantage of being searchable and self-documenting. For example: | ||
`FileBuilder` is a builder and produces objects of type `File`; | ||
* If there is no clear buildee (most likely because it contains multiple finalizers), a noun | ||
describing all the buildable objects suffixed with “Builder” may be used. For example: | ||
`ThreadBuilder`, even though it produces `JoinHandle`s and `JoinGuard`s rather than `Thread`s; | ||
* Finally, if the majority (community) decides there exists a superior name for the builder, but it | ||
does not match any of the guidelines above, generously point out the existence of the builder | ||
in the documentation of buildee and module, if appropriate. | ||
|
||
### Methods | ||
|
||
Option setters – methods which represent options that can be set on the builder – should receive | ||
the builder object as `&mut self` rather than `self` unless there is a good reason to do otherwise. | ||
|
||
Finalizers – methods which produce the built object – should receive self via reference, unless | ||
there is a good reason to do otherwise. | ||
|
||
In order for method listing in documentation to be predictable, methods in the builder’s `impl` | ||
block should be declared following this order: | ||
|
||
* `new` – a method to create the builder object comes first in the `impl` block; | ||
* followed by one or more option setters; | ||
* followed by one or more finalizers. | ||
|
||
This ordering stems from conventional use of the builder pattern: create a builder, set the | ||
options and call a finalizer. | ||
|
||
#### Method naming | ||
|
||
Option setters should be nouns or adjectives. Avoid `is_*` prefix and nouns derived from | ||
adjectives. For example: | ||
|
||
* `name`; | ||
* `stack_size`; | ||
* `read` or `readable` (rather than `is_readable` or `readability`); | ||
* `write` or `writable` (rather than `is_writable` or `writability`). | ||
|
||
Finalizers should be verbs. For example: | ||
|
||
* `spawn`; | ||
* `create`; | ||
* `open`. | ||
|
||
## Proposed changes to the standard library | ||
|
||
### `thread::Builder` | ||
|
||
`thread::Builder` is adjusted to have the following interface: | ||
|
||
```rust | ||
pub struct Builder { … } | ||
impl Builder { | ||
fn new() → Builder; | ||
fn name(&mut self, name: String) → &mut Builder; | ||
fn stack_size(&mut self, size: usize) → &mut Builder; | ||
fn spawn<F>(&mut self, f: F) → Result<JoinHandle> where F: FnOnce(), F: Send + 'static; | ||
fn scoped<'a, T, F>(&mut self, f: F) → Result<JoinGuard<'a, T>> | ||
where T: Send + 'a, F: FnOnce() → T, F: Send + 'a | ||
} | ||
``` | ||
|
||
and `impl Clone for Builder` is provided. | ||
|
||
In summary, all methods are changed to take and return `Builder` by mutable reference rather than | ||
by value. Strictly speaking this is a breaking change, but most users of the `thread::Builder` | ||
should not encounter any breakage. | ||
|
||
### `process::Command` | ||
|
||
`impl Clone for Command` is provided. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the past I've been hesitant to do this as it may prevent us from adding more options to We do have various routes to mitigate this, but this may fall under the category of "would like clone, but implementation may not make it possible". |
||
|
||
### `fs::OpenOptions` | ||
|
||
`OpenOptions` is renamed to `FileBuilder`. `OpenOptions` shall stay as a deprecated alias for | ||
`FileBuilder` for a period. | ||
|
||
# Drawbacks | ||
|
||
Breaking change after 1.0-beta. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At this point it's pretty unlikely that we're going to accept this as a breaking change, but we're definitely open for a deprecation strategy. Would you be ok re-wording the relevant bits of this RFC to recommend a deprecation strategy instead of a breaking change? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure! |
||
|
||
# Alternatives | ||
|
||
* Only apply these guidelines to builders introduced after 1.0-beta; | ||
* Construct builders via a method implemented on the buildees. The API looks like this: | ||
|
||
let file = File::builder() | ||
.write(true) | ||
.truncate(false) | ||
.open(path); | ||
|
||
This allows users of the API never reference the builder type. On the other hand, it is only | ||
suitable for a subset of builders. For example it doesn’t work for `thread::Builder`, because | ||
the finalizers return guards rather than an instance of `Thread`; | ||
|
||
Proposed by [@sfackler][sfackler]. | ||
* Rename `process::Command` to `process::Builder`. | ||
|
||
[sfackler]: https://github.com/rust-lang/rfcs/pull/1044#discussion_r28082235 | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add @sfackler's alternative here as well? I personally wouldn't advocate for it, but it seems like a legitimate alternative! |
||
# Unresolved questions | ||
|
||
None known to the author. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently this is done because the
name
is an owned value (e.g.scoped
/spawn
consumes it), but I do think that this is largely a holdover from the past whereBox<Writer>
orBox<FnOnce>
was contained within and the by-value-ness was needed.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is the primary reason why finalizers taking self by value would be great, if they didn’t introduce usability issues, as you pointed out in the previous revision.