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