Skip to content

Commit 31a6959

Browse files
committed
RFC: Add a replace_with method to Option
1 parent 2619710 commit 31a6959

File tree

1 file changed

+106
-0
lines changed

1 file changed

+106
-0
lines changed

text/0000-option-replace-with.md

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
- Feature Name: `option-replace-with`
2+
- Start Date: 2018-06-28
3+
- RFC PR: (leave this empty)
4+
- Rust Issue: (leave this empty)
5+
6+
# Summary
7+
[summary]: #summary
8+
9+
This RFC proposes the addition of `Option::replace_with` to compliment `Option::replace` ([RFC #2296](https://github.com/rust-lang/rfcs/pull/2296)) and `Option::take` methods. It replaces the actual value in the option with the value returned from a closure given as a parameter, while the old value is passed into the closure.
10+
11+
# Motivation
12+
[motivation]: #motivation
13+
14+
`Option::replace_with` helps to clarify the intent and also [improves the performance of a naive `Option::take` + assignment implementation](https://barrielle.cedeela.fr/research_page/dropping-drops.html).
15+
16+
# Guide-level explanation
17+
[guide-level-explanation]: #guide-level-explanation
18+
19+
`Option::replace_with` is a replacement for the following code:
20+
21+
```rust
22+
let mut some_option: Option<i32> = Some(123);
23+
24+
some_option = consume_option_i32_and_produce_option_i32(some_option.take());
25+
```
26+
27+
With `Option::replace_with` you will write:
28+
29+
```rust
30+
let mut some_option: Option<i32> = Some(123);
31+
32+
some_option.replace_with(|old_value| consume_option_i32_and_produce_option_i32(old_value));
33+
34+
// OR
35+
36+
some_option.replace_with(consume_option_i32_and_produce_option_i32);
37+
```
38+
39+
While the first implementation works fine, it generates suboptimal code due to unnecessary "allocation" and "deallocation" of `None` value. The naive implementation is about 10% slower than the optimal solution:
40+
41+
```rust
42+
let mut some_option: Option<i32> = Some(123);
43+
44+
let old_value = unsafe { mem::uninitialized() };
45+
mem::swap(&mut some_option, old_value);
46+
let mut new_value = consume_option_i32_and_produce_option_i32(old_value);
47+
mem::swap(&mut some_option, &mut new_value);
48+
mem::forget(new_value);
49+
```
50+
51+
`Option::replace_with` can implement the trick and reach the maximum performance.
52+
53+
# Reference-level explanation
54+
[reference-level-explanation]: #reference-level-explanation
55+
56+
This method will be added to the `core::option::Option` type implementation:
57+
58+
```rust
59+
use core::mem;
60+
61+
impl<T> Option<T> {
62+
// ...
63+
64+
#[inline]
65+
fn replace_with<F>(&mut self, f: F)
66+
where
67+
F: FnOnce(Option<T>) -> Option<T>,
68+
{
69+
let mut old_value = unsafe { mem::uninitialized() };
70+
mem::swap(self, &mut old_value);
71+
let mut new_value = f(old_value);
72+
mem::swap(self, &mut new_value);
73+
// After two swaps (`old_value` -> `self` -> `new_value`), `new_value`
74+
// holds an `uninitialized` value, so we just forget about it.
75+
mem::forget(new_value);
76+
}
77+
}
78+
```
79+
80+
Here is a benchmark: [link](https://github.com/frol/rust-benchmark-option-replace_with-rfc).
81+
82+
Here is a generated assembly code comparison in Compiler Explorer: [link](https://godbolt.org/g/6Cukig) (naive implementation is on the left, and optimized implementation is on the right).
83+
84+
# Drawbacks
85+
[drawbacks]: #drawbacks
86+
87+
There will be no need in this method if the compiler can optimize the cases when it is clear that the variable holds `None`, i.e. `Option::take` and simple assignment would not produce unnecessary `moveq 0` and `drop_in_place` call.
88+
89+
# Rationale and alternatives
90+
[alternatives]: #alternatives
91+
92+
The rationale for proposing `Option::replace_with` is that it is the simplest way to boost the performance for the use-case.
93+
94+
The alternative is to teach Rust compiler or LLVM to optimize the use-case expressed with a simple assignment.
95+
96+
# Prior art
97+
[prior-art]: #prior-art
98+
99+
[The performance issue and the workaround were initially discovered](https://barrielle.cedeela.fr/research_page/dropping-drops.html) during the digging into [Completely Unscientific Benchmark](https://www.reddit.com/r/rust/comments/8jbjku/naive_benchmark_treap_implementation_of_c_rust/).
100+
101+
Naive searching through Rust codebase revealed only a single case where currently a simple assignment is used: [`src/librustdoc/passes/collapse_docs.rs`](https://github.com/rust-lang/rust/blob/e3bf634e060bc2f8665878288bcea02008ca346e/src/librustdoc/passes/collapse_docs.rs#L52-L81).
102+
103+
# Unresolved questions
104+
[unresolved]: #unresolved-questions
105+
106+
- Should `Option::replace_with` be introduced or LLVM/Rustc should implement a general optimization which will cover this use-case as well as many others?

0 commit comments

Comments
 (0)