Skip to content

Commit 8583740

Browse files
authored
Merge pull request #5 from rust-lang/feat/code-considerations
Initial code considerations and other sections
2 parents 80ea37f + 4024cdf commit 8583740

30 files changed

+626
-1
lines changed

.travis.yml

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ cache:
88
- book/linkcheck/
99
before_install:
1010
- shopt -s globstar
11-
- MAX_LINE_LENGTH=100 bash ci/check_line_lengths.sh src/**/*.md
1211
install:
1312
- source ~/.cargo/env || true
1413
- cargo install mdbook --version '^0.4.5'

src/SUMMARY.md

+38
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,41 @@
11
# Summary
22

33
[About this guide](./about-this-guide.md)
4+
5+
[Getting started](./getting-started.md)
6+
7+
[Reviewer checklist](./reviewer-checklist.md)
8+
9+
---
10+
11+
- [The feature lifecycle](./feature-lifecycle/summary.md)
12+
- [Landing new features](./feature-lifecycle/new-unstable-features.md)
13+
- [Using tracking issues](./feature-lifecycle/tracking-issues.md)
14+
- [Stabilizing features](./feature-lifecycle/stabilization.md)
15+
- [Deprecating features](./feature-lifecycle/deprecation.md)
16+
17+
---
18+
19+
- [Code considerations](./code-considerations/summary.md)
20+
- [Design](./code-considerations/design/summary.md)
21+
- [Public APIs](./code-considerations/design/public-apis.md)
22+
- [Breaking changes](./code-considerations/breaking-changes/summary.md)
23+
- [Breakage from changing behavior](./code-considerations/breaking-changes/behavior.md)
24+
- [Breakage from new trait impls](./code-considerations/breaking-changes/new-trait-impls.md)
25+
- [`#[fundamental]` types](./code-considerations/breaking-changes/fundamental.md)
26+
- [Safety and soundness](./code-considerations/safety-and-soundness/summary.md)
27+
- [Generics and unsafe](./code-considerations/safety-and-soundness/generics-and-unsafe.md)
28+
- [Drop and `#[may_dangle]`](./code-considerations/safety-and-soundness/may-dangle.md)
29+
- [`std::mem` and exclusive references](./code-considerations/safety-and-soundness/mem-and-exclusive-refs.md)
30+
- [Using unstable language features](./code-considerations/using-unstable-lang/summary.md)
31+
- [Const generics](./code-considerations/using-unstable-lang/const-generics.md)
32+
- [Specialization](./code-considerations/using-unstable-lang/specialization.md)
33+
- [Performance](./code-considerations/performance/summary.md)
34+
- [When to `#[inline]`](./code-considerations/performance/inline.md)
35+
36+
---
37+
38+
- [Tools and bots](./tools-and-bots/summary.md)
39+
- [`@bors`](./tools-and-bots/bors.md)
40+
- [`@rust-timer`](./tools-and-bots/timer.md)
41+
- [`@craterbot`](./tools-and-bots/crater.md)

src/about-this-guide.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# About this guide
22

3+
**Status:** Stub
4+
35
This guide is for contributors and reviewers to Rust's standard library.
46

57
## Other places to find information
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Breakage from changing behavior
2+
3+
Breaking changes aren't just limited to compilation failures. Behavioral changes to stable functions generally can't be accepted. See [the `home_dir` issue](https://github.com/rust-lang/rust/pull/46799) for an example.
4+
5+
An exception is when a behavior is specified in an RFC (such as IETF specifications for IP addresses). If a behavioral change fixes non-conformance then it can be considered a bug fix. In these cases, `@rust-lang/libs` should still be pinged for input.
6+
7+
## For reviewers
8+
9+
Look out for changes in existing implementations for stable functions, especially if assertions in test cases have been changed.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# `#[fundamental]` types
2+
3+
**Status:** Stub
4+
5+
Type annotated with the `#[fundamental]` attribute have different coherence rules. See [RFC 1023](https://rust-lang.github.io/rfcs/1023-rebalancing-coherence.html) for details. That includes:
6+
7+
- `&T`
8+
- `&mut T`
9+
- `Box<T>`
10+
- `Pin<T>`
11+
12+
Typically, the scope of [breakage in new trait impls](./reviewing-code/breakage/new-trait-impls.md) is limited to inference and deref-coercion. New trait impls on `#[fundamental]` types may overlap with downstream impls and cause other kinds of breakage.
13+
14+
[RFC 1023]: https://rust-lang.github.io/rfcs/1023-rebalancing-coherence.html
15+
16+
## For reviewers
17+
18+
Look out for blanket trait implementations for fundamental types, like:
19+
20+
```rust
21+
impl<'a, T> PublicTrait for &'a T
22+
where
23+
T: SomeBound,
24+
{
25+
26+
}
27+
```
28+
29+
unless the blanket implementation is being stabilized along with `PublicTrait`. In cases where we really want to do this, a [crater] run can help estimate the scope of the breakage.
30+
31+
[crater]: ../../tools-and-bots/crater.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Breakage from new trait impls
2+
3+
A lot of PRs to the standard library are adding new impls for already stable traits, which can break consumers in many weird and wonderful ways. The following sections gives some examples of breakage from new trait impls that may not be obvious just from the change made to the standard library.
4+
5+
Also see [`#[fundamental]` types](./fundamental.md) for special considerations for types like `&T`, `&mut T`, `Box<T>`, and other core smart pointers.
6+
7+
## Inference breaks when a second generic impl is introduced
8+
9+
Rust will use the fact that there's only a single impl for a generic trait during inference. This breaks once a second impl makes the type of that generic ambiguous. Say we have:
10+
11+
```rust
12+
// in `std`
13+
impl From<&str> for Arc<str> { .. }
14+
```
15+
16+
```rust
17+
// in an external `lib`
18+
let b = Arc::from("a");
19+
```
20+
21+
then we add:
22+
23+
```diff
24+
impl From<&str> for Arc<str> { .. }
25+
+ impl From<&str> for Arc<String> { .. }
26+
```
27+
28+
then
29+
30+
```rust
31+
let b = Arc::from("a");
32+
```
33+
34+
will no longer compile, because we've previously been relying on inference to figure out the `T` in `Box<T>`.
35+
36+
This kind of breakage can be ok, but a [crater] run should estimate the scope.
37+
38+
## Deref coercion breaks when a new impl is introduced
39+
40+
Rust will use deref coercion to find a valid trait impl if the arguments don't type check directly. This only seems to occur if there's a single impl so introducing a new one may break consumers relying on deref coercion. Say we have:
41+
42+
```rust
43+
// in `std`
44+
impl Add<&str> for String { .. }
45+
46+
impl Deref for String { type Target = str; .. }
47+
```
48+
49+
```rust
50+
// in an external `lib`
51+
let a = String::from("a");
52+
let b = String::from("b");
53+
54+
let c = a + &b;
55+
```
56+
57+
then we add:
58+
59+
```diff
60+
impl Add<&str> for String { .. }
61+
+ impl Add<char> for String { .. }
62+
```
63+
64+
then
65+
66+
```rust
67+
let c = a + &b;
68+
```
69+
70+
will no longer compile, because we won't attempt to use deref to coerce the `&String` into `&str`.
71+
72+
This kind of breakage can be ok, but a [crater](../../tools-and-bots/crater.md) run should estimate the scope.
73+
74+
## For reviewers
75+
76+
Look out for new `#[stable]` trait implementations for existing stable traits.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Breaking changes
2+
3+
Breaking changes should be avoided when possible. [RFC 1105](https://rust-lang.github.io/rfcs/1105-api-evolution.html) lays the foundations for what constitutes a breaking change. Breakage may be deemed acceptable or not based on its actual impact, which can be approximated with a [crater](../../tools-and-bots/crater.md) run.
4+
5+
There are strategies for mitigating breakage depending on the impact.
6+
7+
For changes where the value is high and the impact is high too:
8+
9+
- Using compiler lints to try phase out broken behavior.
10+
11+
If the impact isn't too high:
12+
13+
- Looping in maintainers of broken crates and submitting PRs to fix them.
14+
15+
## For reviewers
16+
17+
Look out for changes to documented behavior and new trait impls for existing stable traits.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Public API design
2+
3+
**Status:** Stub
4+
5+
Standard library APIs typically follow the [API Guidelines](https://rust-lang.github.io/api-guidelines/), which were originally spawned from the standard library itself.
6+
7+
## For reviewers
8+
9+
For new unstable features, look for any prior discussion of the proposed API to see what options and tradeoffs have already been considered. If in doubt, ping `@rust-lang/libs` for input.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Design
2+
3+
**Status:** Stub
4+
5+
Most of the considerations in this guide are quality in some sense. This section has some general advice on maintaining code quality in the standard library.
6+
7+
## For reviewers
8+
9+
Think about how you would implement a feature and whether your approach would differ from what's being proposed. What trade-offs are being made? Is the weighting of those trade-offs the most appropriate?
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# When to `#[inline]`
2+
3+
Inlining is a trade-off between potential execution speed, compile time and code size. There's some discussion about it in [this PR to the `hashbrown` crate](https://github.com/rust-lang/hashbrown/pull/119). From the thread:
4+
5+
> `#[inline]` is very different than simply just an inline hint. As I mentioned before, there's no equivalent in C++ for what `#[inline]` does. In debug mode rustc basically ignores `#[inline]`, pretending you didn't even write it. In release mode the compiler will, by default, codegen an `#[inline]` function into every single referencing codegen unit, and then it will also add `inlinehint`. This means that if you have 16 CGUs and they all reference an item, every single one is getting the entire item's implementation inlined into it.
6+
7+
You can add `#[inline]`:
8+
9+
- To public, small, non-generic functions.
10+
11+
You shouldn't need `#[inline]`:
12+
13+
- On methods that have any generics in scope.
14+
- On methods on traits that don't have a default implementation.
15+
16+
`#[inline]` can always be introduced later, so if you're in doubt they can just be removed.
17+
18+
## What about `#[inline(always)]`?
19+
20+
You should just about never need `#[inline(always)]`. It may be beneficial for private helper methods that are used in a limited number of places or for trivial operators. A micro benchmark should justify the attribute.
21+
22+
## For reviewers
23+
24+
`#[inline]` can always be added later, so if there's any debate about whether it's appropriate feel free to defer it by removing the annotations for a start.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Performance
2+
3+
**Status:** Stub
4+
5+
Changes to hot code might impact performance in consumers, for better or for worse. Appropriate benchmarks should give an idea of how performance characteristics change. For changes that affect `rustc` itself, you can also do a [`rust-timer`](../../tools-and-bots/timer.md) run.
6+
7+
## For reviewers
8+
9+
If a PR is focused on performance then try get some idea of what the impact is. Also consider marking the PR as `rollup=never`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Generics and unsafe
2+
3+
Be careful of generic types that interact with unsafe code. Unless the generic type is bounded by an unsafe trait that specifies its contract, we can't rely on the results of generic types being reliable or correct.
4+
5+
A place where this commonly comes up is with the `RangeBounds` trait. You might assume that the start and end bounds given by a `RangeBounds` implementation will remain the same since it works through shared references. That's not necessarily the case though, an adversarial implementation may change the bounds between calls:
6+
7+
```rust
8+
struct EvilRange(Cell<bool>);
9+
10+
impl RangeBounds<usize> for EvilRange {
11+
fn start_bound(&self) -> Bound<&usize> {
12+
Bound::Included(if self.0.get() {
13+
&1
14+
} else {
15+
self.0.set(true);
16+
&0
17+
})
18+
}
19+
fn end_bound(&self) -> Bound<&usize> {
20+
Bound::Unbounded
21+
}
22+
}
23+
```
24+
25+
This has [caused problems in the past](https://github.com/rust-lang/rust/issues/81138) for code making safety assumptions based on bounds without asserting they stay the same.
26+
27+
Code using generic types to interact with unsafe should try convert them into known types first, then work with those instead of the generic. For our example with `RangeBounds`, this may mean converting into a concrete `Range`, or a tuple of `(Bound, Bound)`.
28+
29+
## For reviewers
30+
31+
Look out for generic functions that also contain unsafe blocks and consider how adversarial implementations of those generics could violate safety.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Drop and `#[may_dangle]`
2+
3+
A generic `Type<T>` that manually implements `Drop` should consider whether a `#[may_dangle]` attribute is appropriate on `T`. The [Nomicon](https://doc.rust-lang.org/nomicon/dropck.html) has some details on what `#[may_dangle]` is all about.
4+
5+
If a generic `Type<T>` has a manual drop implementation that may also involve dropping `T` then dropck needs to know about it. If `Type<T>`'s ownership of `T` is expressed through types that don't drop `T` themselves such as `ManuallyDrop<T>`, `*mut T`, or `MaybeUninit<T>` then `Type<T>` also [needs a `PhantomData<T>` field](https://rust-lang.github.io/rfcs/0769-sound-generic-drop.html#phantom-data) to tell dropck that `T` may be dropped. Types in the standard library that use the internal `Unique<T>` pointer type don't need a `PhantomData<T>` marker field. That's taken care of for them by `Unique<T>`.
6+
7+
As a real-world example of where this can go wrong, consider an `OptionCell<T>` that looks something like this:
8+
9+
```rust
10+
struct OptionCell<T> {
11+
is_init: bool,
12+
value: MaybeUninit<T>,
13+
}
14+
15+
impl<T> Drop for OptionCell<T> {
16+
fn drop(&mut self) {
17+
if self.is_init {
18+
// Safety: `value` is guaranteed to be fully initialized when `is_init` is true.
19+
// Safety: The cell is being dropped, so it can't be accessed again.
20+
unsafe { self.value.assume_init_drop() };
21+
}
22+
}
23+
}
24+
```
25+
26+
Adding a `#[may_dangle]` attribute to this `OptionCell<T>` that didn't have a `PhantomData<T>` marker field opened up [a soundness hole](https://github.com/rust-lang/rust/issues/76367) for `T`'s that didn't strictly outlive the `OptionCell<T>`, and so could be accessed after being dropped in their own `Drop` implementations. The correct application of `#[may_dangle]` also required a `PhantomData<T>` field:
27+
28+
```diff
29+
struct OptionCell<T> {
30+
is_init: bool,
31+
value: MaybeUninit<T>,
32+
+ _marker: PhantomData<T>,
33+
}
34+
35+
- impl<T> Drop for OptionCell<T> {
36+
+ unsafe impl<#[may_dangle] T> Drop for OptionCell<T> {
37+
```
38+
39+
## For reviewers
40+
41+
If there's a manual `Drop` implementation, consider whether `#[may_dangle]` is appropriate. If it is, make sure there's a `PhantomData<T>` too either through `Unique<T>` or as a field directly.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Using `mem` to break assumptions
2+
3+
## `mem::replace` and `mem::swap`
4+
5+
Any value behind a `&mut` reference can be replaced with a new one using `mem::replace` or `mem::swap`, so code shouldn't assume any reachable mutable references can't have their internals changed by replacing.
6+
7+
## `mem::forget`
8+
9+
Rust doesn't guarantee destructors will run when a value is leaked (which can be done with `mem::forget`), so code should avoid relying on them for maintaining safety. Remember, [everyone poops](http://cglab.ca/~abeinges/blah/everyone-poops).
10+
11+
It's ok not to run a destructor when a value is leaked because its storage isn't deallocated or repurposed. If the storage is initialized and is being deallocated or repurposed then destructors need to be run first, because [memory may be pinned](https://doc.rust-lang.org/nightly/std/pin/index.html#drop-guarantee). Having said that, there can still be exceptions for skipping destructors when deallocating if you can guarantee there's never pinning involved.
12+
13+
## For reviewers
14+
15+
If there's a `Drop` impl involved, look out for possible soundness issues that could come from that destructor never running.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Safety and soundness
2+
3+
**Status:** Stub
4+
5+
Unsafe code blocks in the standard library need a comment explaining why they're [ok](https://doc.rust-lang.org/nomicon). There's a lint that checks this. The unsafe code also needs to actually be ok.
6+
7+
The rules around what's sound and what's not can be subtle. See the [Unsafe Code Guidelines WG](https://github.com/rust-lang/unsafe-code-guidelines) for current thinking, and consider pinging `@rust-lang/libs-impl`, `@rust-lang/lang`, and/or somebody from the WG if you're in _any_ doubt. We love debating the soundness of unsafe code, and the more eyes on it the better!
8+
9+
## For reviewers
10+
11+
Look out for any unsafe blocks. If they're optimizations consider whether they're actually necessary. If the unsafe code is necessary then always feel free to ping somebody to help review it.
12+
13+
Look at the level of test coverage for the new unsafe code. Tests do catch bugs!

src/code-considerations/summary.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Code considerations
2+
3+
Code considerations capture our experiences working on the standard library for all contributors. If you come across something new or unexpected then a code consideration is a great place to record it. Then other contributors and reviewers can find it by searching the guide.
4+
5+
## How to write a code consideration
6+
7+
Code considerations are a bit like guidelines. They should try make concrete recommendations that reviewers and contributors can refer to in discussions. A link to a real case where this was discussed or tripped us up is good to include.
8+
9+
Code considerations should also try include a _For reviewers_ section. These can call out specific things to look out for in reviews that could suggest the consideration applies. They can also include advice on how to apply it.
10+
11+
It's more important that we capture these experiences _somehow_ though, so don't be afraid to drop some sketchy notes in and debate the details later!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Using const generics
2+
3+
**Status:** Stub
4+
5+
Complete const generics are currently unstable. You can track their progress [here](https://github.com/rust-lang/rust/issues/44580).
6+
7+
Const generics are ok to use in public APIs, so long as they fit in the [`min_const_generics` subset](https://github.com/rust-lang/rust/issues/74878).
8+
9+
## For reviewers
10+
11+
Look out for const operations on const generics in public APIs like:
12+
13+
```rust
14+
pub fn extend_array<T, const N: usize, const M: usize>(arr: [T; N]) -> [T; N + 1] {
15+
..
16+
}
17+
```
18+
19+
or for const generics that aren't integers, bools, or chars:
20+
21+
```rust
22+
pub fn tag<const S: &'static str>() {
23+
..
24+
}
25+
```

0 commit comments

Comments
 (0)