Skip to content

Commit e43de83

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

File tree

1 file changed

+108
-0
lines changed

1 file changed

+108
-0
lines changed

text/0000-option-replace-with.md

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
This `Option::replace_with` solves only a single case and even than it has limits, e.g. if the function you call inside the closure needs to produce some other value in addition to the value that is going to be a new replacement, [that value cannot "leak" the closure efficiently in safe Rust](https://stackoverflow.com/questions/50985651/how-to-hint-that-a-fnonce-closure-will-be-executed-exactly-once-to-avoid-a-capt).
90+
91+
# Rationale and alternatives
92+
[alternatives]: #alternatives
93+
94+
The rationale for proposing `Option::replace_with` is that it is the simplest way to boost the performance for the use-case.
95+
96+
The alternative is to teach Rust compiler or LLVM to optimize the use-case expressed with a simple assignment.
97+
98+
# Prior art
99+
[prior-art]: #prior-art
100+
101+
[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/).
102+
103+
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).
104+
105+
# Unresolved questions
106+
[unresolved]: #unresolved-questions
107+
108+
- 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)