├── .gitignore ├── .github └── FUNDING.yml ├── Cargo.toml ├── README.md ├── LICENSE └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: sunshowers 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "borrow-complex-key-example" 3 | version = "0.1.0" 4 | authors = ["Rain "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | proptest = "0.9.6" 11 | proptest-derive = "0.1.2" 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example: implementing Borrow for complex keys 2 | 3 | This repository contains a working Rust example for how to implement Borrow for non-trivial keys, written in a literate 4 | programming style. 5 | 6 | Given, for example: 7 | 8 | ```rust 9 | struct OwnedKey { 10 | s: String, 11 | bytes: Vec, 12 | } 13 | 14 | struct BorrowedKey<'a> { 15 | s: &'a str, 16 | bytes: &'a [u8], 17 | } 18 | ``` 19 | 20 | how can you use `BorrowedKey` instances to do lookups for a 21 | `HashSet` or `BTreeSet`? 22 | 23 | Head on over to [`src/lib.rs`](src/lib.rs) to find out! 24 | 25 | ## License 26 | 27 | CC0: https://creativecommons.org/publicdomain/zero/1.0/ 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // borrow-complex-key-example 2 | // 3 | // Written in 2020 by Rain 4 | // 5 | // To the extent possible under law, the author(s) have dedicated all copyright and related and 6 | // neighboring rights to this software to the public domain worldwide. This software is distributed 7 | // without any warranty. 8 | // 9 | // You should have received a copy of the CC0 Public Domain Dedication along with this software. If 10 | // not, see . 11 | 12 | //! An example for how to implement Borrow for complex keys. 13 | //! 14 | //! Thanks to Ivan Dubrov (http://idubrov.name/rust/2018/06/01/tricking-the-hashmap.html) for the 15 | //! inspiration. 16 | 17 | #![allow(unused_imports)] 18 | 19 | use proptest::prelude::*; 20 | use proptest_derive::Arbitrary; 21 | use std::borrow::Borrow; 22 | use std::cmp::Ordering; 23 | use std::collections::HashSet; 24 | use std::collections::hash_map::DefaultHasher; 25 | use std::hash::{Hash, Hasher}; 26 | 27 | #[test] 28 | fn basic() { 29 | // Consider a hash set of strings... 30 | let mut hash_set: HashSet = HashSet::new(); 31 | hash_set.insert("example-string".to_string()); 32 | 33 | // Ordinarily, you need a &String to look up keys. 34 | let string_key: String = "example-string".to_string(); 35 | assert!(hash_set.contains(&string_key)); 36 | 37 | // But, it turns out you can also pass in a &str, not just a &String! 38 | let str_key: &str = "example-string"; 39 | assert!(hash_set.contains(str_key)); 40 | 41 | // How does this work? It's all based on the Borrow trait. 42 | // https://doc.rust-lang.org/std/borrow/trait.Borrow.html 43 | // 44 | // For an owned type O and a borrowed type B, O may implement Borrow if: 45 | // - it's possible to implement a function borrow(&self) -> &B 46 | // - if implemented, Eq, Ord and Hash are *consistent* between O and B. 47 | // 48 | // Intuitively, "consistent" means that O and B have implementations of Eq/Ord/Hash that produce 49 | // the same results. In most cases, this is going to mean that O and B are a 1:1 map. 50 | // 51 | // More formally, "consistent" means that, for *all* values of type O `owned1` and `owned2`, if 52 | // `owned1.borrow()` produces `borrowed1`, and `owned2.borrow()` produces `borrowed2`: 53 | // 54 | // Eq: (owned1 == owned2) is always the same as (borrowed1 == borrowed2). 55 | // Ord: owned1.cmp(owned2) is always the same as borrowed1.cmp(borrowed2). 56 | // Hash: for all hashers, owned1 hashes to the same value as borrowed1 (and owned2 the same as 57 | // borrowed2). 58 | // 59 | // String and str satisfy these conditions (in fact they use the same underlying code), so 60 | // String implements Borrow. 61 | } 62 | 63 | // But what about a user-defined type that's more complex than just a String? For example, 64 | // consider this owned type: 65 | #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Arbitrary)] 66 | struct OwnedKey { 67 | s: String, 68 | bytes: Vec, 69 | } 70 | 71 | // (You might have noticed the "Arbitrary" above. Put a pin in that.) 72 | 73 | // ... and this borrowed type: 74 | #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 75 | struct BorrowedKey<'a> { 76 | s: &'a str, 77 | bytes: &'a [u8], 78 | } 79 | 80 | #[test] 81 | fn complex1() { 82 | // They're basically the same type, modulo ownership. Can we take a hash set of owned keys... 83 | let mut hash_set: HashSet = HashSet::new(); 84 | hash_set.insert(OwnedKey { 85 | s: "foo".to_string(), 86 | bytes: b"abc".to_vec(), 87 | }); 88 | 89 | // and use a borrowed key to look things up, thereby eliminating the need to allocate a new 90 | // owned key just for this? 91 | let _borrowed_key = BorrowedKey { 92 | s: "foo", 93 | bytes: b"abc", 94 | }; 95 | // assert!(hash_set.contains(&_borrowed_key)); 96 | } 97 | 98 | // One's first instinct might be to try and write an impl of this sort. 99 | // (any() is false, so the code below will never be compiled.) 100 | #[cfg(any())] 101 | impl<'a> Borrow> for OwnedKey { 102 | fn borrow(&self) -> &BorrowedKey<'a> { 103 | // ... uhh, what do we put here? We need to return a *reference* to a BorrowedKey. 104 | // But unlike a String/str, there's no BorrowedKey hiding "inside" an OwnedKey. 105 | // Seems like a dead end... 106 | } 107 | } 108 | 109 | // It turns out that we can approach this in a different manner, using the power of trait objects! 110 | // 111 | // Here's how: 112 | // (1) define a trait object that looks like this. 113 | trait Key { 114 | // (The lifetimes can be elided here, but are shown for clarity.) 115 | fn key<'k>(&'k self) -> BorrowedKey<'k>; 116 | } 117 | 118 | // (2) Implement it for both the owned and borrowed versions. 119 | impl Key for OwnedKey { 120 | fn key<'k>(&'k self) -> BorrowedKey<'k> { 121 | BorrowedKey { 122 | s: self.s.as_str(), 123 | bytes: self.bytes.as_slice(), 124 | } 125 | } 126 | } 127 | 128 | impl<'a> Key for BorrowedKey<'a> { 129 | fn key<'k>(&'k self) -> BorrowedKey<'k> { 130 | // This creates a copy of the BorrowedKey with the shorter lifetime 'k. 131 | // 'a can be shortened to 'k because it is a *covariant* lifetime parameter. 132 | // For more about lifetime variance, check out my other tutorial: 133 | // https://github.com/sunshowers/lifetime-variance-example/ 134 | *self 135 | } 136 | } 137 | 138 | // For the rest of this example, we're going to make trait objects of type &(dyn Key + 'a) 139 | // central to our strategy. 140 | // 141 | // OK, so... 142 | // 143 | // (3) Implement Borrow for OwnedKey. 144 | impl<'a> Borrow for OwnedKey { 145 | fn borrow(&self) -> &(dyn Key + 'a) { 146 | // This is a simple coercion from the concrete type to a trait object. 147 | self 148 | } 149 | } 150 | 151 | // Note that while we *could* impl<'a> Borrow for BorrowedKey<'a>, we don't have to. 152 | // https://doc.rust-lang.org/std/collections/struct.HashSet.html#method.contains requires 153 | // T: Borrow. This means that Borrow only needs to be implemented for the type stored in the 154 | // HashSet (or, correspondingly, the key type in a HashMap or BTreeMap). 155 | 156 | // Now, remember that for Borrow to be valid, Eq, Hash and Ord need to be consistent. How do 157 | // we ensure that? Let's see: 158 | 159 | // (4) PartialEq and Eq turn out to be easy to do. 160 | impl<'a> PartialEq for (dyn Key + 'a) { 161 | fn eq(&self, other: &Self) -> bool { 162 | // It's easy to see from the definition that the owned and borrowed types have a consistent 163 | // implementation. (Don't worry, we're actually going to verify this.) 164 | self.key().eq(&other.key()) 165 | } 166 | } 167 | 168 | impl<'a> Eq for (dyn Key + 'a) {} 169 | 170 | // (5) PartialOrd and Ord are similar. 171 | // 172 | // A couple of notes: 173 | // - Importantly, this relies on the fact that the derive implementations for PartialOrd and Ord use 174 | // lexicographic ordering on struct member order. 175 | // - You need to implement this if you're using a btree based data structure, not if you're only 176 | // using hash-based data structures. 177 | impl<'a> PartialOrd for (dyn Key + 'a) { 178 | fn partial_cmp(&self, other: &Self) -> Option { 179 | self.key().partial_cmp(&other.key()) 180 | } 181 | } 182 | 183 | impl<'a> Ord for (dyn Key + 'a) { 184 | fn cmp(&self, other: &Self) -> Ordering { 185 | self.key().cmp(&other.key()) 186 | } 187 | } 188 | 189 | // (6) Hash also turns out to be easy to do in this case, though in some uncommon cases, getting a 190 | // consistent impl may be trickier and may require implementing Hash by hand for the owned type. 191 | // 192 | // Implementing Hash is only necessary if you're using a hash-based data structure. 193 | impl<'a> Hash for (dyn Key + 'a) { 194 | fn hash(&self, state: &mut H) { 195 | self.key().hash(state) 196 | } 197 | } 198 | 199 | // That's it! Now, we have everything we need to do this. 200 | #[test] 201 | fn complex2() { 202 | // This is the same situation as complex1() above. 203 | let mut hash_set: HashSet = HashSet::new(); 204 | hash_set.insert(OwnedKey { 205 | s: "foo".to_string(), 206 | bytes: b"abc".to_vec(), 207 | }); 208 | 209 | let borrowed_key = BorrowedKey { 210 | s: "foo", 211 | bytes: b"abc", 212 | }; 213 | 214 | // And here it is! 215 | // 216 | // &borrowed_key should automatically be coerced into a &dyn Key, but in case it doesn't work, 217 | // you can write: 218 | // 219 | // assert!(hash_set.contains(&borrowed_key as &dyn Key)); 220 | assert!(hash_set.contains(&borrowed_key)); 221 | } 222 | 223 | // ... not so fast, though! We've attempted to satisfy the constraints required for the Borrow impl. 224 | // We've got to test them. 225 | // 226 | // The constraints describe *properties* that must be satisfied. The best way to ensure they are is 227 | // to use property-based testing. 228 | // 229 | // There's much more to property-based testing than we can cover here, but 230 | // https://blog.jessitron.com/2013/04/25/property-based-testing-what-is-it/ is a good intro. 231 | // 232 | // We're going to use the proptest framework to write our property-based tests. 233 | proptest! { 234 | // Here's where that Arbitrary above is useful. It's a simple way to generate random values of 235 | // your structure. 236 | #[test] 237 | fn consistent_borrow(owned1 in any::(), owned2 in any::()) { 238 | // owned1 and owned2 will be populated with random values of OwnedKey. That's enough for us 239 | // to start testing various properties. 240 | // 241 | // Reminder that what we want is for the *owned* and *borrowed* impls to be consistent. 242 | // owned1 and owned2 are the owned keys. The borrowed impls are: 243 | let borrowed1: &dyn Key = &owned1; 244 | let borrowed2: &dyn Key = &owned2; 245 | 246 | // Awesome! That's all the setup we need. Time to test all of this. First, equality: 247 | assert_eq!(owned1 == owned2, borrowed1 == borrowed2, "consistent Eq"); 248 | 249 | // PartialOrd and Ord: 250 | assert_eq!(owned1.partial_cmp(&owned2), borrowed1.partial_cmp(borrowed2), "consistent PartialOrd"); 251 | assert_eq!(owned1.cmp(&owned2), borrowed1.cmp(borrowed2), "consistent Ord"); 252 | 253 | // And finally, Hash. This requires a tiny bit of setup. 254 | fn hash_output(x: impl Hash) -> u64 { 255 | let mut hasher = DefaultHasher::new(); 256 | x.hash(&mut hasher); 257 | hasher.finish() 258 | } 259 | 260 | assert_eq!(hash_output(&owned1), hash_output(&borrowed1), "consistent Hash"); 261 | assert_eq!(hash_output(&owned2), hash_output(&borrowed2), "consistent Hash"); 262 | 263 | // and that's it! Any implementation that satisfies these properties is a valid 264 | // Borrow implementation. A property-based test guarantees that with high confidence. 265 | // 266 | // Here's some stuff to play around with: 267 | // (1) does this work for enums as well? 268 | // (2) try swapping the order of fields in either OwnedKey or BorrowedKey, and see what 269 | // happens to this property test. 270 | } 271 | } 272 | --------------------------------------------------------------------------------