7
7
[ summary ] : #summary
8
8
9
9
Enable the map Entry API to take borrowed keys as arguments, cloning only when
10
- necessary. The proposed implementation introduces a new trait
11
- ` std::borrow::IntoOwned ` which enables the existing ` entry ` methods to accept
12
- borrows. In effect, it makes the following possible:
10
+ necessary (in ` VacantEntry::insert ` ) . The proposed implementation introduces a
11
+ new trait ` std::borrow::AsBorrowOf ` which enables the existing ` entry ` methods
12
+ to accept borrows. In effect, it makes the following possible:
13
13
14
14
``` rust
15
15
let string_map : HashMap <String , u64 > = ... ;
@@ -27,7 +27,8 @@ borrows. In effect, it makes the following possible:
27
27
* nonclone_map . entry (NonCloneable :: new ()). or_insert (0 ) += 1 ; // Can't and doesn't clone.
28
28
```
29
29
30
- See [ playground] ( https://is.gd/0lpGej ) for a concrete demonstration.
30
+ See [ playground] ( https://is.gd/XBVgDe ) and [ prototype
31
+ implementation] ( https://github.com/rust-lang/rust/pull/37143 ) .
31
32
32
33
# Motivation
33
34
[ motivation ] : #motivation
@@ -80,8 +81,6 @@ Specifically we're looking for a fix which supports the following cases
80
81
# Detailed design
81
82
[ design ] : #detailed-design
82
83
83
- [ Playground Proof of Concept] ( https://is.gd/0lpGej )
84
-
85
84
## Approach
86
85
To justify the approach taken by this proposal, first consider the following
87
86
(unworkable) solution:
@@ -94,75 +93,92 @@ To justify the approach taken by this proposal, first consider the following
94
93
```
95
94
96
95
This would support (2 ) and (3 ) but not (1 ) because `ToOwned `'s blanket
97
- implementation requires `Clone `. To work around this limitation we take a trick
98
- out of ` IntoIterator `' s book and add a new ` std :: borrow :: IntoOwned ` trait :
96
+ implementation requires `Clone `. To work around this limitation we define a
97
+ different trait ` std :: borrow :: AsBorrowOf ` :
99
98
100
99
```rust
101
- pub trait IntoOwned < T > {
100
+ pub trait AsBorrowOf < T , B : ? Sized > : Sized where T : Borrow < B > {
102
101
fn into_owned (self ) -> T ;
102
+ fn as_borrow_of (& self ) -> & B ;
103
103
}
104
104
105
- impl <T > IntoOwned <T > for T {
106
- default fn into_owned (self ) -> T { self }
105
+ impl <T > AsBorrowOf <T , T > for T {
106
+ fn into_owned (self ) -> T { self }
107
+ fn as_borrow_of (& self ) -> & Self { self }
107
108
}
108
109
109
- impl <T : RefIntoOwned > IntoOwned <T :: Owned > for T {
110
- default fn into_owned (self ) -> T :: Owned { self . ref_into_owned () }
110
+ impl <'a , B : ToOwned + ? Sized > AsBorrowOf <B :: Owned , B > for & 'a B {
111
+ fn into_owned (self ) -> B :: Owned { self . to_owned () }
112
+ fn as_borrow_of (& self ) -> & B { * self }
111
113
}
114
+ ```
112
115
113
- trait RefIntoOwned {
114
- type Owned : Sized ;
115
- fn ref_into_owned (self ) -> Self :: Owned ;
116
- }
116
+ This trait defines a relationship between three types ` T ` , ` B ` and ` Self ` with
117
+ the following properties:
117
118
118
- impl <'a , T : ? Sized + ToOwned > RefIntoOwned for & 'a T {
119
- type Owned = <T as ToOwned >:: Owned ;
120
- fn ref_into_owned (self ) -> T :: Owned { (* self ). to_owned () }
121
- }
122
- ```
119
+ 1 . There is a by-value conversion ` Self ` -> ` T ` .
120
+ 2 . Both ` T ` and ` Self ` can be borrowed as ` &B ` .
121
+
122
+ These properties are precisely what we need an ` entry ` query: we need (2) to
123
+ hash and/or compare the query against exiting keys in the map and we need (1) to
124
+ convert the query into a key on vacant insertion.
125
+
126
+ The two impl-s capture that
123
127
124
- The auxilliary ` RefIntoOwned ` trait is needed to avoid the coherence issues
125
- which an
128
+ 1 . ` T ` can always be converted to ` T ` and borrowed as ` &T ` . This enables
129
+ by-value keys.
130
+ 2 . ` &B ` can be converted to ` B::Owned ` and borrowed as ` &B ` , when B:
131
+ ` ToOwned ` . This enables borrows of ` Clone ` types.
132
+
133
+ Then we modify the ` entry ` signature (for ` HashMap ` , but similar for ` BTreeMap ` )
134
+ to
126
135
127
136
``` rust
128
- impl <'a , T : ? Sized + ToOwned > IntoOwned <T :: Owned > for & 'a T {
129
- fn into_owned (self ) -> T :: Owned { (* self ). to_owned () }
137
+ pub fn entry <'a , Q , B >(& 'a self , k : Q ) -> Entry <'a , K , V , Q >
138
+ where Q : AsBorrowOf <K , B >
139
+ K : Borrow <B >,
140
+ B : Hash + Eq {
141
+ // use `hash(key.as_borrow_of())` and `key.as_borrow_of() == existing_key.borrow()`
142
+ // for comparisions and `key.into_owned()` on `VacantEntry::insert`.
130
143
}
131
144
```
132
145
133
- implementation would cause. Then we modify the ` entry ` signature to
146
+ ## Detailed changes:
134
147
135
- ``` rust
136
- pub fn entry <'a , Q >(& 'a self , k : Q ) -> Entry <'a , K , V , Q >
137
- where Q : Hash + Eq + IntoOwned <K >
138
- ```
148
+ Also see [ working implementation] ( https://github.com/rust-lang/rust/pull/37143 )
149
+ for diff.
150
+
151
+ 1 . Add ` std::borrow::Borrow ` as described in previous section.
152
+ 2 . Change ` Entry ` to add a ` Q ` type parameter defaulted to ` K ` for backwards
153
+ compatibility (for ` HashMap ` and ` BTreeMap ` ).
154
+ 3 . ` Entry::key ` , ` VacantEntry::key ` and ` VacantEntry::into_key ` are moved to a
155
+ separate ` impl ` block to be implemented only for the ` Q=K ` case.
156
+ 4 . ` Entry::or_insert ` , ` Entry::or_insert_with ` and ` VacantEntry::insert ` gain
157
+ a ` B ` type parameter and appropriate constraints: ` where Q: AsBorrowOf<K, B>, K: Borrow<B>, B: Hash + Eq ` .
139
158
140
- and add a new `Q : IntoOwned <K >` type parameter to `Entry `. This can be done
141
- backwards - compatibly with a `Q = K ` default . The new `Entry ` type will store
142
- `key : Q ` and call `into_owned ` on insert - like calls , while using `Q ` directly on
143
- get - like calls .
144
159
145
160
# Drawbacks
146
161
[ drawbacks ] : #drawbacks
147
162
148
- 1 . The docs of `entry ` get uglier and introduce two new traits the user
149
- never needs to manually implement . If there was support for `where A != B `
150
- clauses we could get rid of the `RefIntoOwned ` trait , but that would still
151
- leave `IntoOwned ` (which is strictly more general than the existing `ToOwned `
152
- trait ). On the other hand `IntoOwned ` may be independently useful in generic
153
- contexts .
163
+ 1 . The docs of ` entry ` get uglier and introduce a new trait the user
164
+ never needs to manually implement.
154
165
155
166
2 . It does not offer a way of recovering a ` !Clone ` key when no ` insert `
156
167
happens. This is somewhat orthogonal though and could be solved in a number
157
- of different ways eg . an `into_key ` method on `Entry ` or via an `IntoOwned `
158
- impl on a `& mut Option <T >`- like type .
168
+ of different ways eg. an ` into_query ` method on ` Entry ` .
169
+
170
+ 4 . The changes to ` entry ` would be insta-stable (not the new traits). There's
171
+ no real way of feature-gating this.
159
172
160
- 3 . Further depend on specialisation in its current form for a public API . If the
161
- exact parameters of specialisation change , and this particular pattern
162
- doesn ' t work anymore , we ' ll have painted ourselves into a corner .
173
+ 5 . May break inference for uses of maps where ` entry ` is the only call ( ` K ` can
174
+ no longer be necessarily inferred as the arugment of ` entry ` ). May also hit
175
+ issue [ # 37138 ] ( https://github.com/rust-lang/rust/issues/37138 ) .
163
176
164
- 4 . The implementation would be insta - stable . There 's no real way of
165
- feature - gating this .
177
+ 6 . The additional ` B ` type parameter on ` on_insert_with ` is a backwards
178
+ compatibility hazard, since it breaks explicit type parameters
179
+ (e.g. ` on_insert_with::<F> ` would need to become ` on_insert_with::<F, _> ` ).
180
+ This seems very unlikely to happen in practice: F is almost always a closure
181
+ and even when it isn't ` on_insert_with ` can always infer the type of ` F ` .
166
182
167
183
# Alternatives
168
184
[ alternatives ] : #alternatives
@@ -177,16 +193,87 @@ get-like calls.
177
193
178
194
3 . Pro: Solves the recovery of ` !Clone ` keys.
179
195
196
+ 2 . Add a ` entry_or_clone ` with an ` Q: Into<Cow<K>> ` bound.
197
+
198
+ 1 . Con: Adds a new method as well as new ` Entry ` types for all maps.
199
+
200
+ 2 . Con: Passes on the problem to any generic users of maps with every layer
201
+ of abstraction needing to provide an ` or_clone ` variant.
202
+
203
+ 3 . Pro: probably clearest backwards compatible solution, doesn't introduce
204
+ any new traits.
205
+
206
+ 3 . Split ` AsBorrowOf ` into ` AsBorrowOf ` and ` IntoOwned ` . This is closer to the
207
+ original proposal:
208
+
209
+ 1 . Con: Requires introducing three new traits.
210
+
211
+ 2 . Con: Requires specialisation to implement a public API, tying us closer
212
+ to current parameters of specialisation.
213
+
214
+ 3 . Pro: ` IntoOwned ` may be independently useful as a more general
215
+ ` ToOwned ` .
216
+
217
+ 4 . Pro: no additional ` B ` type parameter on ` on_insert ` and
218
+ ` on_insert_with ` .
219
+
220
+ Code:
221
+ ``` rust
222
+ pub trait IntoOwned <T > {
223
+ fn into_owned (self ) -> T ;
224
+ }
225
+
226
+ impl <T > IntoOwned <T > for T {
227
+ default fn into_owned (self ) -> Self {
228
+ self
229
+ }
230
+ }
231
+
232
+ impl <T > IntoOwned <T :: Owned > for T
233
+ where T : RefIntoOwned
234
+ {
235
+ default fn into_owned (self ) -> T :: Owned {
236
+ self . ref_into_owned ()
237
+ }
238
+ }
239
+
240
+ pub trait AsBorrowOf <T , B : ? Sized >: IntoOwned <T > where T : Borrow <B > {
241
+ fn as_borrow_of (& self ) -> & B ;
242
+ }
243
+
244
+ impl <T > AsBorrowOf <T , T > for T {
245
+ default fn as_borrow_of (& self ) -> & Self {
246
+ self
247
+ }
248
+ }
249
+
250
+ impl <'a , B : ToOwned + ? Sized > AsBorrowOf <B :: Owned , B > for & 'a B {
251
+ default fn as_borrow_of (& self ) -> & B {
252
+ * self
253
+ }
254
+ }
255
+
256
+ // Auxilliary trait to get around coherence issues.
257
+ pub trait RefIntoOwned {
258
+ type Owned : Sized ;
259
+
260
+ fn ref_into_owned (self ) -> Self :: Owned ;
261
+ }
262
+
263
+ impl <'a , T : ? Sized > RefIntoOwned for & 'a T
264
+ where T : ToOwned
265
+ {
266
+ type Owned = <T as ToOwned >:: Owned ;
267
+
268
+ fn ref_into_owned (self ) -> T :: Owned {
269
+ (* self ). to_owned ()
270
+ }
271
+ }
272
+
273
+ ```
274
+
180
275
# Unresolved questions
181
276
[ unresolved ] : #unresolved-questions
182
277
183
- 1 . Should these traits ever be stabilised ? `RefIntoOwned ` in particular can go
184
- away with the inclusion of `where A != B ` clauses :
185
-
186
- ```rust
187
- impl <'a , T : ? Sized + ToOwned > IntoOwned <T :: Owned > for & 'a T
188
- where T :: Owned != & 'a T
189
- {
190
- fn into_owned (self ) -> T :: Owned { (* self ). to_owned () }
191
- }
192
- ```
278
+ 1 . Are the backwards compatibility hazards acceptable?
279
+ 2 . Is the ` IntoOwned ` version preferable?
0 commit comments