{
"$type": "site.standard.document",
"canonicalUrl": "https://deterministic.space/impl-a-trait-for-str-slices-and-slices-of-strs.html",
"path": "/impl-a-trait-for-str-slices-and-slices-of-strs.html",
"publishedAt": "2017-03-23T00:00:00.000Z",
"site": "at://did:plc:x67qh7v3fd7znbdhauc45ng3/site.standard.publication/3mjcd2t6afe25",
"textContent": "Rust has a pretty powerful type system, but some things are not that easy to express. Follow me on a journey where we try to implement a bit of method overloading by using traits with funny constraints and discover some interesting ways to convince Rust that everything is fine.\n\ntl;dr \"Man this is some type system abuse if I I've ever seen it. I absolutely love it! It'll be useful to know this if I ever need it.\" (/u/mgattozzi [on Reddit][r2])\n\n[r2]: https://www.reddit.com/r/rust/comments/6134oc/how_to_implement_a_trait_for_str_and_str/dfbjhvu/\n\nNote: This post assumes a general understanding of Rust. There will also be some hairy type signatures -- don't be afraid of those! Just skip the parts you don't understand.\n\nSo, let's get started! Our goal is this: We want to have a function that can take both string slices as well a slice of string slices:\n\nThe actual use case is that the string slice can internally be split into subslices – or the user can do it themselves (to make sure it's correct, or to do it programmatically).\n\nWe do this by implementing a new trait[^traits] ToFoo for both types, so our foo function can take any argument that implements ToFoo and use it to convert it to something it can use:\n\n[^traits]: For information about traits, read [this post][tdd], [this chapter][book] in the book, or [this chapter][book2] the in-progress second edition of the book.\n\n[book]: https://doc.rust-lang.org/book/traits.html\n[book2]: http://rust-lang.github.io/book/second-edition/ch10-00-generics.html\n[tdd]: https://deterministic.space/trait-driven-development-in-rust.html\n\nFirst try\n\nSounds easy enough, right? Let's write it down ([playpen][play1]):\n\n[play1]: https://play.rust-lang.org/?gist=a29484d0546d76e09fd3b789df4bf77b&version=stable&backtrace=0\n\nSorry about the whole 'a noise[^lifetimes]! Please ignore this for a minute!\n\n[^lifetimes]: If you are not used to Rust: This is not how most Rust code looks. What are these \"tick a\" things for? I'm glad you asked! One of Rust's defining features is that it is able to ensure that references to x (&x) are valid only as long as the resource x is valid. This prevents some pretty serious bugs! The 'a syntax allows us to give name to these life times so we can, for example, define references &'a x and &'b y and specify that 'a is valid for (at least) as long as 'b by writing 'a: 'b. And usually, it has some pretty nice inference rules for that; in trait definitions however, Rust requires us to be explicit.\n\nBut wait -- this doesn't compile!\n\nSadly, we implemented our trait on a slice (that &[_] thing), but gave it a &[_; 1]. The difference? &[_; 1] is a reference to an array with a known size. We have two options:\n\n1. Use &[\"foo\"][..] to create a slice with an open range, i.e., all elements.\n2. Implement ToFoo for this array type.\n\nThe first option is perfectly valid if it is you who writes writes that foo(&[\"bar\"][..]), but what I am aiming for here is to present a nice API to the user of this theoretical library. And I don't want to tell people to add some magic characters at the end of their argument if I don't have to!\n\nSadly, as of Rust 1.16[^rust-version] we would need to write implementations for _all_ array types we want to support where the type also contains the length of the array! So, one for &[_; 1], another for &[_; 2], and so on. We could do that in a macro, but it'll just generate a whole bunch of code and it not be very elegant.\n\n[^rust-version]: rustc 1.16.0 (30cf806ef 2017-03-10)\n\nAlso, shouldn't it be trivial to represent some &[_, n] as slice? And there are places where that works! Why not here? /u/dbaupp gave a great explanation for this [on Reddit][r1]: It's because we want to use a &self method on &[&str], which means we are dealing with a &&[&str]. And since we are starting with &[&str; 1], we can only rely on coercion for the reference, not the inner [&str; 1].\n\nWe could implement ToFoo on [&str] however, to leverage the fact that the reference in &[\"foo\"] will trigger deref coercions, which means it finds our impl. Sadly, that does not work for functions or method that take a &T where T: ToFoo. So while we can do [\"lorem\"].to_foo(), we can't do foo(&[\"lorem\"]) or even ToFoo::to_foo(&[\"yay\"]) -- which is exactly what we want to use this for...\n\n[r1]: https://www.reddit.com/r/rust/comments/6134oc/how_to_implement_a_trait_for_str_and_str/dfblrm9/\n\nSo, let's try something else instead!\n\nSecond try\n\nRust has a pretty nice collection of conversion traits (see [std::convert]), and one of them is [AsRef], which does \"reference-to-reference conversion\". Basically, you give it an &x and it returns a &y of a compatible type to you:\n\n[std::convert]: https://doc.rust-lang.org/std/convert/index.html\n[AsRef]: https://doc.rust-lang.org/std/convert/trait.AsRef.html\n\nNote that it is not ... for &T but ... for T and then has a method that takes &self. Okay, here's a simplified new version ([playpen][play2]):\n\n[play2]: https://play.rust-lang.org/?gist=9bc7e3895ea82f5842fea82c6d689bb5&version=stable&backtrace=0\n\nAaaand it does not compile:\n\nWhat? \"Conflicting implementation for &str\"? Where? Ohhhhh… There's this impl in std:\n\nSo, Rust is clever enough to see that both &_ and &[_] match that AsRef implementation, but not clever enough differentiate the impl AsRefss to recognize that our second impl ToFoo should only ever work for &[_].\n\nSo, the & is the problem, right?\n\nThird time's the charm\n\nFirst of all, let's repeat our trait signature again, so you don't have to scroll up:\n\nNow, let's implement our trait for str instead of &str. By the way: str is a type that we don't know the size of -- but let's not get hung up on that now.\n\nSee, we're only ever using &str anyway, as our method is taking &self. No need to worry.\n\nNext: Implement the trait for each T where a _reference_ to it implements AsRef<[&str]>:\n\nIt took me quite a while to get to this point. Now, we can use it like this ([playpen][play3]):\n\n[play3]: https://play.rust-lang.org/?gist=82e565d5c1e97f5a8f3635d2672a8beb&version=stable&backtrace=0\n\n(The ?Sized is to allow the str impl.)\n\nNice!\n\nFinally, you can find the real-life code that uses this pattern [in this commit][commit].\n\n[commit]: https://github.com/killercup/assert_cli/commit/a04a0e1a57ee83c7634e6ff1fa8494a8a73b54cd",
"title": "How to implement a trait for &str and &[&str]",
"updatedAt": "2017-03-23T00:00:00.000Z"
}