If you have coded in another language before, you will have used variables and handled different data types. However, Rust does have some quirks that can put off developers. This is especially true if the developer has come from a dynamic language, as these quirks mainly revolve around memory management and reference to variables. These can be intimidating initially, but when you get to understand them, you will learn to appreciate them.
Some people might hear about these quirks and wonder why they should bother with the language at all. This is understandable, but these quirks are why Rust is such a paradigm-shifting language. Working with borrow checking and wrestling with concepts such as lifetimes and references gives us the high-level memory safety of a dynamic language, such as Python. However, we can also get memory-safe, low-level resources, such as those delivered by C and C++.
This means that we do not have to worry about dangling pointers, buffer overflows, null pointers, segmentation faults, data races, and other issues when coding in Rust. Issues such as null pointers and data races can be hard to debug. The borrow-checking rules enforced are a good trade-off, as we must learn Rust’s quirks to get the speed and control of non-memory-safe languages, but we do not get the headaches these non-memory-safe languages introduce.
Before we do any web development, we need to run our first program. We can do this in the Rust playground at https://play.rust-lang.org/ .
If you have never visited the Rust playground before, you will see the following layout once you are there:
fn main () {
println! ("hello world" );
}
The preceding code will look like the following screenshot in the online Rust playground, after we have pressed the RUN button on the top left-hand side of the screen:
Figure 1.2 – View of the online Rust playground
In our hello world code, what we have is a main function, which is our entry point. This function fires when we run our program. All programs have entry points. If you have not heard of the concept before, due to coming from a dynamic language, the entry point is the script file that you point your interpreter at. For Python, a closer analogy would be the main block that runs if the file is directly run by the interpreter, denoted as follows:
if __name__ == " __main__" :
print ("Hello, World!" )
If you were to code in Python, you would probably see this used in a Flask application.
Right now, we have not done anything new. This is a standard Hello, World! example with a little change in syntax; however, even with this example, the string that we print is not all that it seems. For instance, let us write our own function that accepts a string and prints it out with the following code:
fn print (message: str ) {
println! ("{}" , message);
}
fn main () {
let message = "hello world" ;
print (message);
}
This code should work in other interpreted languages, such as Python or JavaScript. We pass it into our function and print it. However, if we do print it, we get the following printout:
10 | print(message);
| ^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `Sized` is not implemented for `str`
= note: all function arguments must have a statically known size
This is not very straightforward, but it brings us to the first area we must understand if we are to code in Rust, which is strings. Don’t worry; strings are the quirkiest variables that you need to get your head around to write functional Rust code.
Using strings in Rust
Before we explore the error in the previous section, let us rectify it so that we know what to work toward. We can get the print function to work without any errors with the following code:
fn print (message: String ) {
println! ("{}" , message);
}
fn main () {
let message = String ::from ("hello world" );
print (message);
}
What we did was create a String from "hello world" and pass it into the print function. This time, the compiler did not throw an error because we always know the size of a String, so we can keep the right amount of memory free for it. This may sound counterintuitive because strings are usually of different lengths; it would not be a very flexible programming language if we were only allowed to use the same length of letters for every string in our code. We can make all strings have the same size of memory by passing round pointers, implemented as a vector of bytes, which in Rust is denoted as Vec<u8>. This holds a reference to the string content (str, also known as a string slice) in the heap memory, as seen in the following figure:
Figure 1.3 – A string’s relationship to str “one”
We can see in Figure 1.3 that a string is a vector of three numbers. One is the actual memory address of the str it references. The second number is the size of the memory allocated, and the third is the length of the string content. Therefore, we can access string literals in our code without having to pass variables of various sizes around our code. We know that String has a set size and, therefore, can allocate this size in the print function parameter. Note that String is on the stack memory while our string literal is on the heap memory.
Considering that we know that String has a set size while our string literal varies, we can deduce that the stack memory is used for predictable memory sizes and is allocated ahead of time when the program runs. The stack memory allocation order is decided on compilation and optimized by the compiler. Our heap memory is dynamic, and therefore, memory is allocated when it is needed.
Now that we know the basics of strings, we can use the different ways in which they are created, as seen in the following code:
let string_one = "hello world" .to_owned ();
let string_two = "hello world" .to_string ();
let string_three = string_two.clone ();
Note that creating string_three is costly, as we must copy the underlying data in the heap, and heap operations are expensive. This is not a unique quirk of Rust. In our example, we are just experiencing what happens under the hood. For instance, if we alter strings in Python, we will have different outcomes:
data = ["one" , "two" , "three" , " four" ]
string = ""
for i in data:
string += i
"" .join(data)
Looping through and adding the strings is slower because Python must allocate new memory and copy an entire string to that new memory address. The join method is faster because Python can allocate the memory of all the data of the list and then copy over the strings in the array, meaning the string must only be copied once. This shows us that although high-level languages like Python may not force you to think about the memory allocation of strings, you will still end up paying the price if you don’t acknowledge it.
We can pass a string literal into the print function by borrowing it, as seen in the following Rust code:
fn print (message: &str ) {
println! ("{}" , message);
}
fn main () {
let message : &str = "hello world" ;
print (message);
}
The : &str is a type annotation of the type of variable that is being assigned to message. We do not need the type annotation; the compiler will work out the type automatically. However, for our exercise, it does illustrate what is happening. The borrow is denoted by &. We will cover borrowing later in the chapter. For now, however, we can deduce that the borrow is only a fixed-size reference to a variable-sized string slice. If the borrow was not a fixed size, we would not be able to pass it into the print function because we would not know the size.
At this point, we can comfortably use strings in Rust productively. The next concept that we must understand before we start writing Rust programs is integers and floats.
Using integers and floats
In most high-level web programming languages, we just assign a float or integer to a variable name and move on with the program. However, from what we have been exposed to in the previous section on strings, we now understand that we must worry about memory size when using strings in Rust. This is no different with integers and floats. We know that integers and floats have a range of sizes. Therefore, we must tell Rust what we pass around our code.
Rust supports signed integers, which are denoted by i, and unsigned integers, which are denoted by u. Both unsigned and signed integers consist of 8, 16, 32, 64, or 128 bits. We will cover what unsigned and signed integers are in this section.
8-byte integers
Exploring the math behind numbers being represented in binary is not relevant for this book; however, we do need to understand the range of numbers that can be represented with several bits, as this will help us understand what the different types of floats and integers in Rust denote.
Because binary is either a 0 or a 1, we can calculate the range of unsigned integers that can be represented by the bits, by raising 2 to the power of the number of bits we have. For example, if we have an integer that is represented by 8 bits, 2 to the power of 8 equates to 256. We must remember that 0 is also represented. Considering this, an integer of 8 bits has a range of 0 to 255. We can test this calculation with the following code:
let number : u8 = 256 ;
This is one higher than the range that we calculated. As a result, we should not be surprised to see the following overflow error:
the literal `256` does not fit into the type
`u8` whose range is `0..=255`
So we can deduce that if we lower the unsigned integer to 255, it will pass. However, let’s say we change the unsigned integer into a signed integer with the following code:
let number : i8 = 255 ;
We will see that we get a helpful error message, as follows:
the literal `255` does not fit into the type
`i8` whose range is `-128..=127`
With this helpful error message, we can see that a signed integer considers negative numbers, so the absolute value that a signed integer can take is roughly half.
16-byte integers
We can increase the range by assigning the number as a 16-bit signed integer with the following code:
let number : i16 = 255 ;
This would work. However, let us add our 16-bit integer with our 8-bit integer, using the following code:
let number = 255i16 ;
let number_two = 5i8 ;
let result = number + number_two;
The previous code might look a little different to you. All we have done in the preceding code is define the data type with a suffix instead. So number has a value of 255 and a type of i16, and number_two has a value of 5 and a type of i8. If we run the previous code, we get the following error:
11 | let result = number + number_two;
| ^ no implementation for `i16 + i8`
|
= help: the trait `Add<i8>` is not implemented for `i16`
We will cover traits later in this chapter. For now, all we must understand is that we cannot add the two different integers. If they were both the same type, then we could.
We can change the integer type through casting, using as, as seen in the following line of code:
let result = number + number_two as i16 ;
This means that number_two is now a 16-bit integer, and result will be 260. However, we must be careful with casting because if we were to do it the wrong way, we could end up with a silent bug, which is unusual for Rust.
Embrace the errors when learning Rust
Rust’s strict compilation process can seem daunting, but it’s a key feature that prevents runtime failures common in other languages. Rust refuses to compile code with errors, ensuring safety and reliability. By encountering and understanding these errors early, you can learn Rust more effectively.
Trying out code variants without error warnings can lead to confusion and frustration later. Teaching Rust by showing errors upfront and explaining them helps us to grasp the language’s principles, making our learning process smoother and more engaging.
If we cast number as i8 instead of casting number_two as i16, then result would equate to 4, which does not make sense because 255 + 5 equals 260. This is because i8 is smaller than i16. So, if we cast an i16 integer as an i8 integer, we are essentially chopping off some of the data, by just taking the lower bits of the number and disregarding the upper bits. Therefore, number ends up being a -1 if we cast it to an i8 integer. To be safer, we can use the i8::from function, as seen in the following code:
let result = i8 ::from (number) + number_two;
Running this will give us the following error:
let result = i8::from(number) + number_two;
| ^^^^^^^^ the trait `From<i16>` is not
implemented for `i8`
Again, we will go over traits later on in the chapter, but we can see in the preceding code that because the trait From<i16> is not implemented for an i8 integer, we cannot cast an i8 integer into an i16 integer. With this understanding, we are free to work with integers safely and productively.
Type system to the rescue
Type errors for multiplying two different numerical types prevent silent bugs. If the type system did not flag the differences, then we could have bugs that get thrown when the program is running or, even worse, silent bugs where we don’t know that there is a bug.
Previously, I worked on a financial loss calculation engine. If the number was not loaded to the correct precision of the spec, then the calculations at the end of the financial loss model would be wildly different. The type system here ensures that we do not multiply a number that is a different precision to the spec defined in the code.
Introducing floats
One last point about integer sizes in Rust is that they are not continuous. The supported sizes are shown in the following table:
Bits
Calculation
Size
8
2^8
256
16
2^16
65536
32
2^32
4294967296
64
2^64
1.8446744e+19
128
2^128
3.4028237e+38
Table 1.2 – Size of integer types
When it comes to floats, Rust accommodates f32 and f64 floating point numbers. Both these floating-point types support negative and positive values. Declaring a floating-point variable requires the same syntax as integers, as seen in the following code:
let float : f32 = 2.6 ;
With this, we can comfortably work with integers and floats in our Rust code. However, we know as developers that just declaring floats and integers is not very useful. We want to be able to contain and loop through them. In the next section, we will do just that with vectors and arrays.
Storing data in arrays
In Rust, we can store our floats, integers, and strings in arrays and vectors. First, we will focus on arrays.
Arrays are stored in stack memory. Knowing this, and remembering what we learned about strings, we can deduce that arrays are of a fixed size. This is because, as we remember, if a variable is stored on the stack, then the memory is allocated on compilation and loaded into the stack when the program starts. We can define an array of integers, loop through it, print each integer, and then access an integer by index with the following code:
fn main () {
let int_array : [i32 ; 3 ] = [1 , 2 , 3 ];
for i in int_array {
println! ("{}" , i);
}
println! ("{}" , int_array[1 ]);
}
With the previous code, we define the type and size by wrapping them in square brackets. For instance, if we were going to create an array of floats with a length of 4, we would use int_array: [f32; 4] = [1.1, 2.2, 3.3, 4.4]. Running the preceding code will give you the following printout:
1
2
3
2
In the preceding printout, we can see that the loop works and we can access the second integer with square brackets. Although the memory size of the array is fixed, we can still change it. This is where mutability comes in.
When we define a variable as mutable, it means that we can mutate it. In other words, we can alter the value of the variable after it has been defined if it is mutable. If you tried to update any of the variables in the code that we have written in this chapter, you will have realized that you can’t. This is because all variables in Rust are immutable by default. However, we can make any variable in Rust mutable by putting a mut tag in front of the variable name.
Going back to the fixed array, we cannot change the size of it, meaning we cannot append/push new integers to it due to it being stored in stack memory. However, if we define a mutable array, we can update parts of it with other integers that are the same memory size. An example of this is the following code:
fn main () {
let mut mutable_array : [i32 ; 3 ] = [1 , 2 , 0 ];
mutable_array[2 ] = 3 ;
println! ("{:?}" , mutable_array);
println! ("{}" , mutable_array.len ());
}
In the preceding code, we can see that the last integer in our array is updated to 3. We then print out the full array and then the length. You may have also noted that the first print statement of the preceding code now employs {:?}. This calls the Debug trait. If Debug is implemented for the thing that we are trying to print, then the full representation of the thing we print is displayed in the console. You can also see that we print out the result of the length of the array. Running this code will give the following printout:
[1, 2, 3]
3
With the preceding printout, we can confirm that the array is now updated. We can also access slices with our arrays. To demonstrate this, we can create an array of 100 zeros. We can then take a slice of this and print it out with the following code:
fn main () {
let slice_array : [i32 ; 100 ] = [0 ; 100 ];
println! ("length: {}" , slice_array.len ());
println! ("slice: {:?}" , &slice_array[5 .. 8 ]);
}
Running the preceding code will result in the following printout:
length: 100
slice: [0, 0, 0]
We are now able to be productive with arrays. Arrays can be useful for caching. For instance, if we know the amount that we need to store, then we can use arrays effectively. However, we have only managed to store one type of data in the array. If we tried to store strings and integers in the same array, we would have a problem. How would we define the type? This problem applies to all collections, such as vectors and HashMaps . There are multiple ways to do this, but the most straightforward is using enums.
Storing data in vectors
What we have covered with arrays can be applied to vectors. The only difference is that we do not have to define the length and can increase the size of the vector if needed. This is because vectors put their data onto the heap, meaning that vectors are pointers, pointing to a collection of items in heap memory that are all next to each other, making vectors fast for looping through and reading data. If the allocated memory on the heap runs out, the vector can allocate a bigger section of memory and copy the existing data over to the new memory address. To demonstrate this flexibility, we will create a vector of strings and then add a string to the end, with the following code:
let mut string_vector : Vec <&str > = vec! ["one" , "two" , "three" ];
println! ("{:?}" , string_vector);
string_vector.push ("four" );
println! ("{:?}" , string_vector);
In the preceding code, we can see that we use the vec! macro to create the vector of strings. You may have noticed with macros such as vec! and println! that we can vary the number of inputs. We will cover macros later in the chapter. Running the preceding code will result in the following printout:
["one", "two", "three"]
["one", "two", "three", "four"]
We can also create an empty vector with the new function from the Vec struct, with let _empty_vector: Vec<&str> = Vec::new();.
You may be wondering when to use vectors and when to use arrays. Vectors are more flexible. You may be tempted to opt for arrays for performance gains. At face value, this seems logical, as arrays are stored in the stack. Accessing the stack is going to be quicker because the memory sizes can be computed at compile time, making the allocation and deallocation simpler compared to the heap. However, because it is on the stack, it cannot outlive the scope that it is allocated. Moving a vector around would require moving a pointer around. However, moving an array requires copying the whole array. Therefore, copying fixed-size arrays is more expensive than moving a vector. If you have a small amount of data that you only need in a small scope and you know the size of the data, then reaching for an array does make sense. However, if you’re going to be moving the data around, even if you know the size of the data, using vectors is a better choice.
We must also take note of our initial introduction of vectors. If the heap memory allocated for a vector runs out, the data is copied over to a new address. This copying over is not free. If you know the size of the data you need, you can prevent this copying with a Vec::with_capacity(100) constructor, initially setting aside the memory you need. 100 is just an example of a size of items to be put in a vector; you have the freedom to input whatever value you need. Don’t worry if you exceed this later; the memory reallocation will still work.
Now that we can be productive with basic collections, we can move on to a more advanced collection, a HashMap.
Mapping data with enums
Enums are, well, enums. In dynamic languages such as Python, you may not have had to use them, due to being able to pass any type anywhere you want. However, they are still available. Enum is short for enumerated type and basically defines a type with possible variants. In our case, we want our array to store strings and integers in the same collection. We can do this by initially defining our enum with the following code:
enum SomeValue {
StringValue (String ),
IntValue (i32 )
}
In the preceding code, we can see that we defined an enum with the name of SomeValue. We then denoted that StringValue holds the value of a string and that IntValue holds the value of an integer. We can then define an array with a length of 4, consisting of two strings and two integers, with the following code:
let multi_array : [SomeValue; 4 ] = [
SomeValue::StringValue (String ::from ("one" )),
SomeValue::IntValue (2 ),
SomeValue::StringValue (String ::from ("three" )),
SomeValue::IntValue (4 )
];
In the preceding code, we can see that we wrap our strings and integers in our enum. Now, looping through and exporting it is going to be another task. For instance, there are things that we can do to an integer that we cannot do to a string, and vice versa. Considering this, we are going to have to use a match statement when looping through the array, as seen in the following code:
for i in multi_array {
match i {
SomeValue::StringValue (data) => {
println! ("The string is: {}" , data);
},
SomeValue::IntValue (data) => {
println! ("The int is: {}" , data);
}
}
}
In the preceding code, we can see that if i is SomeValue::StringValue, we then assign the data wrapped in SomeValue::StringValue to the variable name data. We then pass data into the inner scope to be printed. We use the same approach with our integer. Even though we are merely printing to demonstrate the concept, we can do anything in these inner scopes to the data variable that the type allows us to. Running the preceding code gives the following printout:
The string is: one
The int is: 2
The string is: three
The int is: 4
Using enums to wrap data and match statements to handle them can be applied to HashMaps and vectors.
Mapping data with HashMaps
In some other languages, HashMaps are referred to as dictionaries. They have a key and a value. We can insert and get values using the key. Now that we have learned about handling collections, we can get a little more adventurous in this section.
We can create a simple profile of a game character. In this character profile, we are going to have a name, age, and a list of items that they have. This means that we need an enum that houses a string, an integer, and a vector that also houses strings. We will want to print out the complete HashMap to see if our code is correct in one glance. To do this, we are going to implement the Debug trait for our enum, as seen in the following code:
#[derive(Debug)]
enum CharacterValue {
Name (String ),
Age (i32 ),
Items (Vec <String >)
}
In the preceding code, we can see that we have annotated our enum with the derive attribute. An attribute is metadata that can be applied to the CharacterValue enum in this case. The derive attribute tells the compiler to provide a basic implementation of a trait. So, in the preceding code, we are telling the compiler to apply the basic implementation of Debug to the CharacterValue enum. With this, we can then create a new HashMap that has keys pointing to the values we defined in the preceding code with the following:
use std::collections::HashMap;
fn main () {
let mut profile : HashMap<&str , CharacterValue> = HashMap::new ();
}
We stated that it is mutable because we are going to insert values with the following code:
profile.insert ("name" , CharacterValue::Name ("Maxwell" .to_string ()));
profile.insert ("age" , CharacterValue::Age (34 ));
profile.insert ("items" , CharacterValue::Items (vec! [
"laptop" .to_string (),
"book" .to_string (),
"coat" .to_string ()
]));
println! ("{:?}" , profile);
We can see that we have inserted all the data that we need. Running this would give us the following printout:
{"items": Items(["laptop", "book", "coat"]), "age": Age(34),
"name": Name("Maxwell")}
In the preceding output, we can see that our data is correct. Inserting it is one thing; however, we now must get it out again. We can do this with a get associated function.
Associated functions are functions attached to a type, such as an enum, struct, or datatype. They can reference the entity that they are associated with, or even consume them. In some cases, a reference to the type they are associated with isn’t needed at all. Later on in the book, we will associate functions with database handle structs where the functions have no reference to the struct.
The get associated function returns an Option type. The Option type returns either Some or None. So if we were to get name from our HashMap, we would need to do two matches, as seen in the following code:
match profile.get ("name" ) {
Some (value_data) => {
match value_data {
CharacterValue::Name (name) => {
println! ("the name is: {}" , name);
},
_ => panic! ("name should be a string" )
}
},
None => {
println! ("name is not present" );
}
}
In the preceding code, we can check to see if there is a name in the keys. If there is not, then we just print out that it was not present. If the name key is present, we then move on to our second check, which prints out the name if it is CharacterValue::Name. However, there is something wrong if the name key does not house CharacterValue::Name. So, we add only one more check to match, which is _. This is a catch, meaning anything else. We are not interested in anything other than CharacterValue::Name. Therefore, the _ catch maps to a panic! macro, which essentially throws an error.
We could make this shorter. If we know that the name key is going to be in the HashMap, we can employ the unwrap function with the following code:
match profile.get ("name" ).unwrap () {
CharacterValue::Name (name) => {
println! ("the name is: {}" , name);
},
_ => panic! ("name should be a string" )
}
The unwrap function directly exposes the result. However, if the result is None, then it will directly result in an error terminating the program, which would look like the following printout:
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value'
Using the unwrap function is risky, and we should try and avoid it as much as possible. We can avoid using the unwrap function by handling our results and errors, which we cover in the next section.
Handling results and errors
In the previous section, we learned that directly unwrapping Option, resulting in None, panics a thread. There is another outcome that can also throw an error if unsuccessfully unwrapped, and this is Result. The Result type can return either Ok or Err. To demonstrate this, we can create a basic function that returns a Result type, based on a simple Boolean that we pass into it, with the following code:
fn error_check (check: bool ) -> Result <i8 , &'static str > {
if check {
Err ("this is an error" )
}
else {
Ok (1 )
}
}
In the preceding code, we can see that we return Result<i8, &'static str>. This means that we return an integer if Result is Ok, or we return an integer if Result is Err. The &'static str variable is basically our error string. We can tell that it’s a reference because of &. The 'static part means that the reference is valid for the entire lifetime of the running program. If this does not make sense now, don’t worry; we will cover lifetimes later in the chapter.
Now that we have created our error-checking function, we can test to see what these outcomes look like with the following code:
fn main () {
println! ("{:?}" , error_check (false ));
println! ("{:?}" , error_check (false ).is_err ());
println! ("{:?}" , error_check (true ));
println! ("{:?}" , error_check (true ).is_err ());
}
Running the preceding code gives us the following printout:
Ok(1)
false
Err("this is an error")
true
In the preceding output, we can see that it returned exactly what we wanted. Also note that we can run the is_err() function on Result variable, resulting in false if we return Ok or true if we return Err. We can also directly unwrap but add extra tracing to the stack trace with the following expect function:
let result : i8 = error_check (true ).expect ("this has been caught" );
The preceding function will result in the following printout:
thread 'main' panicked at 'this has been caught: "this is an error"'
Through the preceding example, we can see that we get the message from the expect function first, and then the error message returned in Result. With this understanding, we can throw, handle, and add extra tracing to errors.
We can now throw errors, but how can we handle them? An obvious choice would be a match statement, but we can also use the ? operator. For instance, let us return the same return type for another function that calls our error_check function, as seen in the following code:
fn error_check_two (check: bool ) -> Result <i8 , &'static str > {
let outcome : i8 = error_check (check)?;
Ok (outcome)
}
Here, we can see that the ? operator is used when calling error_check(check)?. What happens here is that if the error_check function returns an error, that error is just directly returned as an error for the error_check_two function. If the error_check function returns an Ok result, then this is automatically unwrapped and assigned to the variable outcome. This will save you a lot of code. Throughout the book, we will map errors so that we can exploit the ? operator, instead of writing matches everywhere. We will even configure our errors to automatically construct an HTTP response. It’s a complete myth that we need to write a lot of Rust code for web programming.
Rust data types and variables cheatsheet
Strings : The String type is heap-allocated and its size is known, whereas &str is a fixed-size reference to a string slice. Use String::from or .to_string() to create a String. Use &str to borrow a string slice without ownership transfer.
Integers and floats : Rust requires explicit size declarations for integers (e.g., i8, u16) and floats (e.g., f32 and f64). Different integer sizes have specific ranges (e.g., u8 ranges from 0 to 255, and i8 ranges from -128 to 127). Type errors are caught at compile time to prevent runtime bugs.
Mutability : Variables are immutable by default. Use mut to make a variable mutable, allowing its value to be changed.
Arrays : Fixed-size, stack-allocated collections. Here’s an example: let int_array: [i32; 3] = [1, 2, 3];
Vectors : Dynamic-size, heap-allocated collections. Here’s an example: let mut vec = Vec::new(); vec.push(1);
Enums : Used to define a type with possible variants. Here’s an example: enum SomeValue { StringValue(String), IntValue(i32) }
Result type : Used for functions that can return an error. Here’s an example: fn error_check(check: bool) -> Result<i8, &'static str> { ... }
Error handling : Use match, unwrap, or the ? operator to handle results and propagate errors.
However, we are getting more exposed to lifetimes and borrow references as we move forward. Now is the time to address this by understanding variable ownership.