|
| 1 | +# Use borrowed types for arguments |
| 2 | + |
| 3 | +## Description |
| 4 | + |
| 5 | +Using a target of a deref coercion can increase the flexibility of your code when you are deciding which argument type to use for a function argument. |
| 6 | +In this way, the function will accept more input types. |
| 7 | + |
| 8 | +This is not limited to slice-able or fat pointer types. In fact you should always prefer using the __borrowed type__ over __borrowing the owned type__. E.g., `&str` over `&String`, `&[T]` over `&Vec<T>`, or `&T` over `&Box<T>`. |
| 9 | + |
| 10 | +Using borrowed types you can avoid layers of indirection for those instances where the owned type already provides a layer of indirection. For instance, a `String` has a layer of indirection, so a `&String` will have two layers of indrection. |
| 11 | +We can avoid this by using `&str` instead, and letting `&String` coerce to a `&str` whenever the function is invoked. |
| 12 | + |
| 13 | +## Example |
| 14 | + |
| 15 | +For this example, we will illustrate some differences for using `&String` as a function argument versus using a `&str`, but the ideas apply as well to using `&Vec<T>` versus using a `&[T]` or using a `&T` versus a `&Box<T>`. |
| 16 | + |
| 17 | +Consider an example where we wish to determine if a word contains three consecutive vowels. |
| 18 | +We don't need to own the string to determine this, so we will take a reference. |
| 19 | + |
| 20 | +The code might look something like this: |
| 21 | + |
| 22 | +```rust |
| 23 | +fn three_vowels(word: &String) -> bool { |
| 24 | + let mut vowel_count = 0; |
| 25 | + for c in word.chars() { |
| 26 | + match c { |
| 27 | + 'a' | 'e' | 'i' | 'o' | 'u' => { |
| 28 | + vowel_count += 1; |
| 29 | + if vowel_count >= 3 { |
| 30 | + return true |
| 31 | + } |
| 32 | + } |
| 33 | + _ => vowel_count = 0 |
| 34 | + } |
| 35 | + } |
| 36 | + false |
| 37 | +} |
| 38 | + |
| 39 | +fn main() { |
| 40 | + let ferris = "Ferris".to_string(); |
| 41 | + let curious = "Curious".to_string(); |
| 42 | + println!("{}: {}", ferris, three_vowels(&ferris)); |
| 43 | + println!("{}: {}", curious, three_vowels(&curious)); |
| 44 | + |
| 45 | + // This works fine, but the following two lines would fail: |
| 46 | + // println!("Ferris: {}", three_vowels("Ferris")); |
| 47 | + // println!("Curious: {}", three_vowels("Curious")); |
| 48 | + |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +This works fine because we are passing a `&String` type as a parameter. |
| 53 | +If we comment in the last two lines this example fails because a `&str` type will not coerce to a `&String` type. |
| 54 | +We can fix this by simply modifying the type for our argument. |
| 55 | + |
| 56 | +For instance, if we change our function declaration to: |
| 57 | + |
| 58 | +```rust, ignore |
| 59 | +fn three_vowels(word: &str) -> bool { |
| 60 | +``` |
| 61 | + |
| 62 | +then both versions will compile and print the same output. |
| 63 | + |
| 64 | +```bash |
| 65 | +Ferris: false |
| 66 | +Curious: true |
| 67 | +``` |
| 68 | + |
| 69 | +But wait, that's not all! There is more to this story. |
| 70 | +It's likely that you may say to yourself: that doesn't matter, I will never be using a `&'static str` as an input anways (as we did when we used `"Ferris"`). |
| 71 | +Even ignoring this special example, you may still find that using `&str` will give you more flexibility than using a `&String`. |
| 72 | + |
| 73 | +Let's now take an example where someone gives us a sentence, and we want to determine if any of the words in the sentence has a word that contains three consecutive vowels. |
| 74 | +We probably should make use of the function we have already defined and simply feed in each word from the sentence. |
| 75 | + |
| 76 | +An example of this could look like this: |
| 77 | + |
| 78 | +```rust |
| 79 | +fn three_vowels(word: &str) -> bool { |
| 80 | + let mut vowel_count = 0; |
| 81 | + for c in word.chars() { |
| 82 | + match c { |
| 83 | + 'a' | 'e' | 'i' | 'o' | 'u' => { |
| 84 | + vowel_count += 1; |
| 85 | + if vowel_count >= 3 { |
| 86 | + return true |
| 87 | + } |
| 88 | + } |
| 89 | + _ => vowel_count = 0 |
| 90 | + } |
| 91 | + } |
| 92 | + false |
| 93 | +} |
| 94 | + |
| 95 | +fn main() { |
| 96 | + let sentence_string = |
| 97 | + "Once upon a time, there was a friendly curious crab named Ferris".to_string(); |
| 98 | + for word in sentence_string.split(' ') { |
| 99 | + if three_vowels(word) { |
| 100 | + println!("{} has three consecutive vowels!", word); |
| 101 | + } |
| 102 | + } |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +Running this example using our function declared with an argument type `&str` will yield |
| 107 | + |
| 108 | +```bash |
| 109 | +curious has three consecutive vowels! |
| 110 | +``` |
| 111 | + |
| 112 | +However, this example will not run when our function is declared with an argument type `&String`. |
| 113 | +This is because string slices are a `&str` and not a `&String` which would require an allocation to be converted to `&String` which is not implicit, whereas converting from `String` to `&str` is cheap and implicit. |
| 114 | + |
| 115 | +## See also |
| 116 | +- [Rust Language Reference on Type Coercions](https://doc.rust-lang.org/reference/type-coercions.html) |
| 117 | +- For more discussion on how to handle `String` and `&str` see [this blog series (2015)](https://web.archive.org/web/20201112023149/https://hermanradtke.com/2015/05/03/string-vs-str-in-rust-functions.html) by Herman J. Radtke III. |
0 commit comments