In this video, learn how to use the bitwise NOT, AND, OR, XOR, and SHIFT operators to manipulate individual bits. This is essential to learn because bitwise operations are commonly used for low-level data manipulation.
- [Instructor] Remember that under the hood, a computer uses sequences of binary bits, or ones and zeros to represent information. We can define values in Rust using a binary notation with the prefix 0b, followed by a sequence of ones and zeros, as shown here on line two. Written out like this, this kind of sequence can be visually hard to interpret. So Rust allows us to insert optional underscore characters to visually break it up into chunks so it's easier to read. Rust will store this value as an integer. And by default, it will use the signed 32-bit integer data type. However, this sequence is only eight bits long, so we could store the value in a smaller integer data type, such as a u8 if we needed to conserve memory. Let's do that by adding the suffix u8 to the end of the literal to specify its data type as an unsigned eight-bit integer. Now, since this is being stored as an integer, when we use the print line macro on line three to display its value, Rust will format it like any other integer. Rust doesn't care that we initially defined it using binary notation. If we want to display its value as a sequence of bits, we can include additional formatting in the curly braces. I'll copy and paste that print line statement and then add some formatting. The colon in the curly braces indicates we want to use special formatting. The lowercase b tells it to display the values as binary bits, or ones and zeros. Eight is the number of bits to display. And the zero in front of that tells it to show any leading zeros on the front of the number rather than hide them which is the default behavior. When I run this program, the first line of output shows that this particular sequence of bits corresponds to the integer value 245 when displayed using decimal notation. And then the second line shows it represented as binary bits. Now the programming operations we use to process data, like addition and subtraction, are applied to groupings of multiple bits, which are most commonly thought of in terms of bytes, or a byte is a group of eight related bits. However, there are certain programming situations where we may want to think about and manipulate data in terms of individual bits, rather than larger sets of bytes representing some data. This type of bitwise thinking is especially common in low-level and embedded programming. For example, digital input and output pins on a microcontroller are often organized into groups called a port. The port shown here has eight physical pins numbered zero through seven. The microcontroller has a data direction register which holds eight bits in memory, representing the direction of those eight digital IO pins. When a bit is set to one, that tells the microcontroller to configure and use the corresponding pin as input to the device. And when it's set to zero, that means the pin will be used for output. As the programmer, we need a way to work with these bits on an individual basis to configure the microcontroller. To do that, we'll use bitwise operators, which apply logical operations on a pattern of bits at the individual bit level. Rust includes the common set of bitwise operators with NOT, AND, OR, XOR, and Shift. And we'll look how to use each of these starting with NOT. The NOT operation is applied to a single bit to invert its value. So if the input bit A is one, the output from the NOT operation will be zero and vice versa. If we apply it to a sequence of bits, the bitwise NOT operator will look at all the bits individually and produce an output sequence where all of the bits have been flipped. We can apply the bitwise NOT operator in Rust using an exclamation mark or bang symbol. This will invert the bits in value and store the result. I'll copy and paste the print statement to display the result after that. And since we're changing value, we'll also need to mark it as mutable. Now I can run the program. And we can see that the bits have been inverted between these last two lines. The next operator is bitwise AND, which takes two input bits, A and B. And if both A and B are one, then the output of the AND operator will also be one. Otherwise, for any other combination of A and B containing a zero, the result of the AND operation will be zero. One common use for bitwise AND is to clear the value of a specific bit. Let's say we want to change the value of the bit at this position to be zero while maintaining the current value of the rest of these bits. If we bitwise AND that sequence with another sequence containing all ones, except for the bit we want to change to zero, the existing value for all the bits we want to maintain will effectively pass through, while the bit we want to clear is set to zero in the output. In Rust, the bitwise AND operator is represented using a single ampersand. This code will apply the operation we just examined, applying the bitwise AND to clear the bit at position three. I'll paste the print statement again after that to view the result. Run the code and we can see that bit at position three gets set to zero in the final output. Another common use for bitwise AND is to check a specific value within a sequence to see whether it's one or zero. If we want to check the value at this position, we can bitwise AND to the sequence with a pattern of zeros and a one at the position to check. If the result of that operation is all zeros, then we know the bit we wanted to check was also zero. Now, if we do the same thing to check a different bit in that sequence, whose value is one, we see that now the output contains a one. Interpreted as an integer, this would represent a non-zero number. In low-level programming, this type of operation is often used to check status registers which hold a collection of bits representing different things about the current state of the processor. For a code example, let's use bitwise AND to check and display the value of the bit at position six in our value. Notice that we're directly using the result of that operation in the print macro, and not assigning the result back to the variable because we just want to read that bit, not modify its value. When I run this code, we see that bit six is zero. A partner to the bitwise AND operation is bitwise OR, which also operates on two inputs, A and B. If either or both of the input bits are one, then the output will also be one. Otherwise, if both inputs are zero, the output of A or B will be zero. This operation can be used if we want to change a specific bit to set its value to one. For example, let's set this bit at position six to one. By applying the bitwise OR with a sequence of zeros, and one at the position we want to set, the previous value for all the other bits will be maintained while the value of the bit we want to set will always be one in the output. Rust uses the vertical pipe symbol to represent the bitwise OR operator. So this code will set the value of bit six to one, and save the result back to the value variable. When I run it, we can see that bit change from zero to one in the output. Our next operator is bitwise XOR, which stands for exclusive or. I'd like to think of it like a more selective cousin of our bitwise OR. It also operates on two input bits. And the output of XOR will be one when the two input bits are different. And it will be zero if both of the inputs are the same. So you can use XOR to check if and where the values may differ between two bit patterns. The bitwise OR operator is represented using the carrot symbol or hat operator. To demonstrate let's, XOR the value with a sequence of alternating zeros and ones. When I run this program, the resulting value shows us which bits in the previous value differed from that alternating sequence. The final two operators will look at our bitwise shifts which can be used to shift a bit pattern left or right by a specific number of bits. The shift operator will shift in zeros to backfill the spaces leftover from shifting bits around. We can apply the left shift operator to our value which is represented using two less than symbols, followed by the number of bits we want to shift the value by. Let's shift it left by four. I'll paste the print statement again. And then after that, let's use the right shift operator which is represented with two greater than symbols to shift the sequence right by two bits. And of course display the result. I'll run that. And looking at the last three lines of code, we can see that when we shifted the sequence left by four bits, the values that were shifted off the left side are lost and zeros were inserted on the right side of the sequence, to backfill those shifted bits. Then when we shifted our value right by two bits, on the last line, zeros are used to backfill the shifted bits on the left side, which means we effectively lost this one bit when it was shifted off the left side. So those are the six main bitwise operators in Rust. NOT, AND, OR, XOR, Left Shift, and Right Shift. Commonly used for low level programming when you need to manipulate bits on an individual basis