- In the previous section,
we started looking at string slices.
As we know, a string slice refers to some text
you've allocated elsewhere.
We're gonna look at some techniques
with string slices now in this section.
So when you create a string slice into some text,
the string slice contains two pieces of information.
It contains the address of one of the bytes in the text,
and it also knows the length of the slice in bytes,
and it's quite useful.
You can get this information.
Like here, I've got a string literal,
"hello" with a smiley face,
and so let me just draw that string literal here,
"hello" smiley face.
Our s is a string slice.
So a string slice, remember, is a fat pointer.
It has a pointer to a byte,
and then it has the length of the string.
Let's call it n like that, the length of the slice,
and when you've got a string slice,
there's an as_ptr function, as pointer.
It'll give you the address that it's pointing to,
and then you can print that using the print formatter,
the pointer formatter, okay?
So it'll give you the address of the first byte.
You can also get the length of the slice.
How big is this slice in bytes?
And also, of course, you can print out the slice
as it stands, like I've done here.
So I'm gonna run lots of demos.
I'm gonna run four separate examples in this section.
So let's have a look at this one first.
So here's the code for the chapter, lesson07_borrowing,
and in main, I'm gonna uncomment this statement here,
demo_string_slice_techniques.
Okay, and that's here.
I've got four functions.
We're going to look at basic slice usage,
and then some other techniques later on.
So I'll just comment out the other functions just for now.
We'll come and look at those later,
and let's have a look at the slice_usage function.
Okay, so I create a string slice, s1,
into that literal text.
I could have also created an actual String object.
This would've worked equally well
for an actual String object, not just a slice.
So I can get the address of the first byte in the text
and display it as a pointer.
I can get the length of my slice
in bytes and display it there,
and I can display the whole text for the string as well.
So if I run this,
don't think there's gonna be any great surprises here.
Let's just see. Well, I say that.
It outputs the pointer. That's interesting.
Notice though that the length is nine.
Now, you probably weren't expecting that.
We'll talk about how this works in a moment.
It actually required nine bytes to represent that string,
not six like I might imagine, but nine,
and then it output the text hello smiley face.
So we need to understand how strings actually work
in terms of encoding.
So in Rust, strings are encoded as UTF-8.
In other words, a string or a character
is an unsigned eight-bit integer, okay?
And some characters can be simply represented in one byte,
like the letter H only requires one byte to represent it,
but more complex characters like a smiley face
might require more bytes.
So the smiley face requires four bytes to represent it.
So that means when you iterate over a string,
you've got two different ways you can iterate.
You can iterate over the raw bytes if you wanted to.
You can say, what's the byte? What's the value of each byte?
Or you can iterate over the characters,
which might be a bit more useful.
So I'm gonna show you an example
of both of those types of iteration.
First of all, have a look here.
S is my string slice, "hello" smiley face,
and the string slice has a bytes method,
and it'll give you each individual byte.
So this loop is actually going to iterate nine times.
It'll iterate over the simple characters,
and it'll iterate four times over the smiley face
because that smiley face is actually four bytes internally,
and it'll output each byte individually.
That might be useful.
Accessing bytes might be useful if you're interacting
with some kind of low-level API,
might actually want to call some kind of C API
where you need to access individual bytes.
This might be the way to do it,
but from a more high-level point of view,
it's probably easier if you iterate
over the actual characters.
So here, if I iterate over the characters,
there's a .chars method.
It'll give you back a whole character at a time.
So this will give me back an h, then an e,
then the l, then the l, then the o,
then the smiley face will come back as a single character.
So this is going to iterate six times.
This smiley face will be one character.
So let's see an example of both of these techniques.
Here we are back in the code.
I'm going to uncomment the second function, slice_iteration,
and here it is, slice_iteration.
I'm gonna run it so we can then see what it output,
what the output looks like.
So first of all, here's my string slice.
It would work just as well with actual String objects.
So you can take your string slice or the String object
and treat it as bytes.
That's gonna iterate nine times.
There were nine bytes in this slice, as we saw just now.
What I've done for each byte,
I output the value in decimal and hexadecimal and octal.
I output the same byte in those formats.
So that's the letter h in decimal, octal,
and so hexadecimal and octal,
and this is the letter e in decimal, hexadecimal, octal.
That's the letter l. That's the other letter l.
That's the letter o, and then this, these are the first,
these are the four bytes that constitute the smiley face
as individual bytes.
Okay, or alternatively,
you can iterate over the actual characters themselves,
which is kind of more user friendly.
So in that case, we get back in the whole character.
That's four bytes' worth, but it's one logical character.
Rust understands the internal formatting.
All right, so that was actually quite useful to discuss.
The next thing we're going to look at is
how you can get back part of a string.
Okay, so let's discuss the theory for that.
This is a bit like what you can do in Python.
You can actually get back not the whole text
but part of a text.
You can specify the start index and the end index
that you want to get back as byte positions,
not as character positions, but as byte positions.
So it's quite low level.
What you do is you specify the start index
as an inclusive number, and the default is zero.
The end index is exclusive,
and it defaults to the end of the string.
The syntax looks like this. You have some kind of string.
You can specify there's my string message.
This is a string slice.
You can say I want the start index
and whatever number that might be,
.., and then some end index,
and what you do is you basically take an & there.
This means gimme back a slice of the text
starting at this position and ending at that position,
or rather, one before that position
because the end index is exclusive.
So I'm gonna show you some examples of that. Let's see.
Slice_part_of_string.
So let me let you see what the code looks like,
and while you're looking at that,
I'm just going to do a cargo run.
Okay then, so my string is howdy.
I think that's going to be nine bytes, five for the letters,
and then four bytes for the sunglass man.
So here I take a slice starting at position zero
up to but not including three.
So that's going to be position zero, one, two.
That's going to be "how."
That slice is what I'm gonna get back.
S3 is going to be that slice there.
I output for s3 the pointer where it starts.
I output the length of the slice.
Okay, the length of the slice there.
Well, the length of the slice
is going to be three, isn't it?
Zero, one and two.
It has a length of three,
and the text that it contains in this slice
from bytes zero to two, really,
is going to be zero, one, two.
That's the text that's going to be in this slice.
So s3 just gives me that piece of text.
The whole text was "howdy" smiley face,
but this slice is only looking at part of it.
S4 is going to be the same, actually.
If you don't specify the start index,
then it defaults to zero.
So that would actually be the same.
S4 would be the same as s3.
S4 is also "how,"
and then here I'm getting
from byte position two up to four, really,
so zero, one, two.
So it'll be two, three, four. It'll be "wdy."
That's also got a length of three bytes.
Okay, so a length of three, "wdy,"
and then finally, I get a slice that starts at position two
and goes to the end.
Position two is the w, and it goes to the end.
So that's going to be seven bytes, isn't it?
Because that's four bytes.
The smiley face is four bytes, and plus those three,
that's gonna be seven bytes altogether.
That's the length of that slice,
and the text of that slice is "wdy"
man with sunglasses or person sunglasses.
So that's really the point of string slices.
You don't own the text.
You just have a pointer into a viewport,
and you know the length of that viewport in bytes.
Very good.
Right, one last thing to look at, slice_mutability.
So that's going to be the last thing we'll discuss
in this little section, slice_mutability.
Let's look at the theory first.
So imagine you've got a mutable string,
a string that you've created using the mut keyword.
You can define a mutable string slice.
You say &mut to make it a mutable reference,
and you use str to mean it's a string slice
not a whole string, but just a slice of a string.
This is what it looks like.
In this example, I've got a mutable string called message.
Okay, and it points to "croeso,"
which you know means welcome in Wales,
"croeso," and then s is a mutable string slice.
So you take a mutable reference to the string,
but the slice, remember, is like a fat pointer
to the text itself.
So the slice will have a pointer
that points to the letter c,
and it'll have a length of six, okay?
So that basically is my string slice s,
and then I can use my string slice.
On my string slice,
I can call functions like make uppercase.
It would convert this string into uppercase,
"CROESO," like so.
I'll run an example of that.
It's quite straightforward actually,
but it's nice to see an example.
So slice_mutability,
I'll run the example so you can see the output,
and then we'll discuss.
So here's my initial mutable string, "croeso,"
and then, oh, I append some other text.
"Croeso o gymru" means welcome from Wales.
So then in here,
I've got a mutable reference only visible inside this block,
goes outta scope there, and my mutable reference,
my mutable string slice, oh, it refers to,
it's a mutable reference into characters nine onwards,
so not the whole text.
It just looks at part of the text
from position nine onwards.
Well, let's see, zero, one, two, three, four, five,
six, seven, eight, nine.
So basically it starts, my slice starts at the g,
and it goes to the end of the string.
The g is position nine.
So my slice is just looking at "gymru,"
and it converts that slice to uppercase.
So just this part of the actual text itself
will be converted to uppercase, as we can see here,
"croeso o GYMRU."
Okay, so hopefully by now,
you've got a better feel for why string slices are useful.
They give you access to a window of text
which you can access and slice and dice as you need.