Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ci/dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@ WeatherForecast
webpage
WebSocket
whitespace
whitespaces
wildcard
wildcards
Wirth
Expand Down
11 changes: 5 additions & 6 deletions listings/ch04-understanding-ownership/listing-04-07/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
// ANCHOR: here
fn first_word(s: &String) -> usize {
// ANCHOR: as_bytes
let bytes = s.as_bytes();
// ANCHOR_END: as_bytes

// ANCHOR: char_indices
let char_indices = s.char_indices();
// ANCHOR_END: char_indices
// ANCHOR: iter
for (i, &item) in bytes.iter().enumerate() {
for (i, ch) in char_indices {
// ANCHOR_END: iter
// ANCHOR: inside_for
if item == b' ' {
if ch.is_whitespace() {
return i;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
for (i, ch) in s.char_indices() {
if ch.is_whitespace() {
return i;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
// ANCHOR: here
fn first_word(s: &str) -> &str {
// ANCHOR_END: here
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
for (i, ch) in s.char_indices() {
if ch.is_whitespace() {
return &s[..i];
}
}

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "ownership"
version = "0.1.0"
edition = "2024"

[dependencies]
62 changes: 62 additions & 0 deletions listings/ch04-understanding-ownership/listing-04-10/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// ANCHOR: here
fn nth_word(s: &str, n: usize) -> &str {
// The minimum acceptable value for `n` is 1.
if n == 0 {
return s;
}
let mut n = n;
let mut word_start_index = 0;
// This flag is set to `true` when skipping leading spaces or spaces between words
let mut skipping_spaces = true;
for (i, ch) in s.char_indices() {
if skipping_spaces {
if ch.is_whitespace() {
continue;
}
skipping_spaces = false;
word_start_index = i;
} else if ch.is_whitespace() {
// End of current word
n -= 1;
if n == 0 {
return &s[word_start_index..i];
}
// Skip spaces before looking for the next word
skipping_spaces = true;
} else if i + 1 == s.len() && n == 1 {
// We are at end of `s` and are looking for the last word
return &s[word_start_index..];
}
}
s
}
// ANCHOR_END: here

// ANCHOR: usage
fn main() {
// Entire word should be returned when looking for 0th word or
// when looking for a word beyond words in the input
assert_eq!(nth_word("1 2 3", 0), "1 2 3");
assert_eq!(nth_word("1 2 3 4", 5), "1 2 3 4");

// Input word should be returned when no word exists
assert_eq!(nth_word("", 1), "");
assert_eq!(nth_word(" ", 1), " ");

// Entire word should be returned when there's only 1 word
assert_eq!(nth_word("s", 1), "s");
assert_eq!(nth_word("hello", 1), "hello");

// Correct word should be returned irrespective of number of whitespaces
assert_eq!(nth_word("hello ", 1), "hello");
assert_eq!(nth_word(" hello ", 1), "hello");
assert_eq!(nth_word("\t \nhello", 1), "hello");

// Correct word should be returned when there are multiple words
assert_eq!(nth_word("so how are you?", 1), "so");
assert_eq!(nth_word("so how are you?", 2), "how");
assert_eq!(nth_word("so how are you?", 3), "are");
assert_eq!(nth_word("so how are you?", 4), "you?");
assert_eq!(nth_word(" hello how are ", 3), "are");
}
// ANCHOR_END: usage
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// ANCHOR: here
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
for (i, ch) in s.char_indices() {
if ch.is_whitespace() {
return &s[..i];
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();

for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];

for (i, ch) in s.char_indices() {
if ch.is_whitespace() {
return &s[..i];
}
}

Expand Down
58 changes: 29 additions & 29 deletions src/ch04-03-slices.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,10 @@ _Slices_ let you reference a contiguous sequence of elements in a
of reference, so it does not have ownership.

Here’s a small programming problem: Write a function that takes a string of
words separated by spaces and returns the first word it finds in that string.
If the function doesn’t find a space in the string, the whole string must be
words separated by whitespaces and returns the first word it finds in that string.
If the function doesn’t find a whitespace in the string, the whole string must be
one word, so the entire string should be returned.

> Note: For the purposes of introducing slices, we are assuming ASCII only in
> this section; a more thorough discussion of UTF-8 handling is in the
> [“Storing UTF-8 Encoded Text with Strings”][strings]<!-- ignore --> section
> of Chapter 8.

Let’s work through how we’d write the signature of this function without using
slices, to understand the problem that slices will solve:

Expand All @@ -28,50 +23,41 @@ clear as we keep going.) But what should we return? We don’t really have a way
to talk about *part* of a string. However, we could return the index of the end
of the word, indicated by a space. Let’s try that, as shown in Listing 4-7.

<Listing number="4-7" file-name="src/main.rs" caption="The `first_word` function that returns a byte index value into the `String` parameter">
<Listing number="4-7" file-name="src/main.rs" caption="The `first_word` function that returns a char index value into the `String` parameter">

```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:here}}
```

</Listing>

Because we need to go through the `String` element by element and check whether
a value is a space, we’ll convert our `String` to an array of bytes using the
`as_bytes` method.
Because we need to go through the `String` one character at a time and check whether
the character is a whitespace, we’ll iterator over our `String` using `char_indices` method.

```rust,ignore
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:as_bytes}}
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:char_indices}}
```

Next, we create an iterator over the array of bytes using the `iter` method:
Next, iterate over the `(character, index)` tuples using `char_indices`:

```rust,ignore
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:iter}}
```

We’ll discuss iterators in more detail in [Chapter 13][ch13]<!-- ignore -->.
For now, know that `iter` is a method that returns each element in a collection
and that `enumerate` wraps the result of `iter` and returns each element as
part of a tuple instead. The first element of the tuple returned from
`enumerate` is the index, and the second element is a reference to the element.
This is a bit more convenient than calculating the index ourselves.

Because the `enumerate` method returns a tuple, we can use patterns to
destructure that tuple. We’ll be discussing patterns more in [Chapter
6][ch6]<!-- ignore -->. In the `for` loop, we specify a pattern that has `i`
for the index in the tuple and `&item` for the single byte in the tuple.
Because we get a reference to the element from `.iter().enumerate()`, we use
`&` in the pattern.

Inside the `for` loop, we search for the byte that represents the space by
using the byte literal syntax. If we find a space, we return the position.
Otherwise, we return the length of the string by using `s.len()`.
For now, know that `char_indices` is an `iterator` over each character in the string and its position. The first element of the tuple returned from `iterator` is the index, and the second element is the character. This is a bit more convenient than calculating the index ourselves.

Since a tuple is returned, we can use patterns to destructure that tuple. We’ll be discussing patterns more in [Chapter 6][ch6]<!-- ignore -->. In the `for` loop, we specify a pattern that has `i` for the index in the tuple and `ch` for the character in the tuple.

Inside the `for` loop, we search for the char that represents the whitespace by
using `is_whitespace` method. If we find a whitespace, we return the end of first word.
Otherwise, we return the string length as end of first word.

```rust,ignore
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-07/src/main.rs:inside_for}}
```


We now have a way to find out the index of the end of the first word in the
string, but there’s a problem. We’re returning a `usize` on its own, but it’s
only a meaningful number in the context of the `&String`. In other words,
Expand Down Expand Up @@ -291,6 +277,20 @@ makes our API more general and useful without losing any functionality:

</Listing>

#### Solution to getting the Nth word

Here's a solution to get the Nth word within a string slice accounting for multiple whitespaces and leading whitespaces:

<Listing number="4-10" file-name="src/main.rs" caption="Returning the nth word inside a string">

```rust
{{#rustdoc_include ../listings/ch04-understanding-ownership/listing-04-10/src/main.rs:here}}
```

</Listing>



### Other Slices

String slices, as you might imagine, are specific to strings. But there’s a
Expand Down