Skip to content

Commit c232127

Browse files
cbreedenmarcoienilambda-fairysimonsanpickfire
authored
Use coercion for arguments (#29)
Describe some of the reasons it is considered idiomatic to use `&str` over `&String` in most cases. Co-authored-by: Marco Ieni <[email protected]> Co-authored-by: Chris Wong <[email protected]> Co-authored-by: simonsan <[email protected]> Co-authored-by: Ivan Tham <[email protected]>
1 parent 1a65aad commit c232127

File tree

2 files changed

+118
-0
lines changed

2 files changed

+118
-0
lines changed

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- [Introduction](./intro.md)
44

55
- [Idioms](./idioms/index.md)
6+
- [Use borrowed types for arguments](./idioms/coercion-arguments.md)
67
- [Concatenating Strings with `format!`](./idioms/concat-format.md)
78
- [Constructor](./idioms/ctor.md)
89
- [The `Default` Trait](./idioms/default.md)

idioms/coercion-arguments.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
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

Comments
 (0)