COP 3515 - Lesson 22a - Quiz 3 Notes PDF
Document Details
Uploaded by UnmatchedJadeite2405
University of South Florida
Tags
Summary
This document provides notes from a lesson on C/Rust programming. Topics covered include the history of Rust, what Rust is, and why Rust is used in programming. The quiz is related to these notes.
Full Transcript
An Introduction To The C/Rust Programming Languages Lesson #22: Quiz #3 Notes 1 The Secret Of The Red Dot 2 An Introduction To The C/Rust Programming Languages Lesson #17:...
An Introduction To The C/Rust Programming Languages Lesson #22: Quiz #3 Notes 1 The Secret Of The Red Dot 2 An Introduction To The C/Rust Programming Languages Lesson #17: Cargo, Comments, Variables, Data Types, Mutable / Immutable 3 History Of Rust Rust started as a personal language that was developed by Graydon Hoare who worked for Mozilla in 2006. Rust was officially recognized as a Mozilla sponsored project in 2009 and was first publicly announced in 2010. The first pre-alpha version of Rust was released in January of 2012. The current stable version of Rust is version 1.16. The operating systems supported by Rust include: Linux, Windows, macOS, Android, IOS, etc. 4 What Is Rust? Graydon Hoare called Rust a "safe, concurrent, and practical language" that supports the functional and imperative paradigms. Rust’s syntax is comparable to that of the C++ programming language. Rust is free and open-source software, which means that anybody may use it for free, and the source code is openly provided so that anyone can enhance the product’s design. There is no such thing as direct memory management, such as calloc or malloc - Rust manages memory internally. Rust was developed to deliver excellent performance comparable to C and C++ while prioritizing code safety, which is the Achilles’ heel of the other two languages. Rust Is Built On Other Langauges 6 Why Use Rust? Rust is a strongly typed programming language that prioritizes speed and safety and extraordinarily safe concurrency and memory management. Rust tackles two long-standing concerns for C/C++ developers: memory errors and concurrent programming. This is regarded as its primary advantage. Rust manages memory internally - trash collection is not required. In Rust, each reference has a lifespan, which specifies the scope for which that reference is valid. Over the last 12 years memory safety concerns have accounted for over 70% of all security flaws in Microsoft products, the necessity of proper memory management becomes instantly clear. Why Use Rust? Like C, Rust helps control low-level details as a systems programming language. Using Rust means that we’re safe against resource leakage problems. Because Rust does not have an active garbage collector, other programming languages may utilize its projects as libraries via foreign- function interfaces. This is a good case for existing projects where high performance while preserving memory safety is crucial. In such cases, Rust code may replace select areas of software where speed is critical without rebuilding the entire product. Why Use Rust – The NSA Memo On 11/10/22 the NSA released a memo entitled "Software Memory Safety" Cybersecurity Information Sheet In it they made the following statements: – Memory issues in software comprise a large portion of the exploitable vulnerabilities in existence. – NSA advises organizations to consider making a strategic shift from programming languages that provide little or no inherent memory protection, such as C/C++, to a memory safe language when possible. – Memory safe languages were identified as being: C#, Go, Java®, Ruby , Rust®, and Swift® 9 The Stack & The Heap All data stored Data without a on the stack known size at must have a compile time or known fixed with a size that size at may change compile time. will be stored FIFO. on the heap. Rust stores variables on either its stack or its heap The behavior (speed, size, etc.) is different between the two options. We will discuss this in detail later on. 10 Image Credit: https://medium.com/@Miguel_Grillo/stack-vs-heap-and-the-virtual-memory-ea075ea6e3fc HELLO WORLD AND VARIABLES "Hello World" In Rust The Rust Hello World program looks like this: fn main() { println!("Hello, world!"); } Picking Apart "Hello World" fn – The fn is short for "function." In Rust (and most other programming languages), a function means "tell me some information, and I’ll do some things and give you an answer." main – The main function is a special function: just like in C, it’s where your program starts. () – These parentheses are the parameter list for this function. It’s empty right now, meaning there are no parameters. Every left parenthesis has a matching right. {} – These are called curly braces or brackets. We actually need to give the main function a body. The body lives inside these braces. The body will say what the main function actually does. Every left curly brace has a matching right. Picking Apart "Hello World" println! – This is a macro. It means "print and add a new line." Macros are very similar to functions - the difference is that it ends with an exclamation point (!). Rust has a print! Macro that stays on the same line after printing. ("Hello, world!") – This is the parameter list for the macro call. We’re saying "call this macro called println with these parameters." "Hello, world!" – This is a string. Just like in C strings are a bunch of letters (or characters) put together. We put them inside the double quotes (") to mark them as strings. ; – This is a semicolon. It completes a statement. Statements do something specific. In this case, it’s calling the macro. Rust Interpolation Def: interpolation - the insertion of something of a different nature into something else. In Rust, in order to include other values in what we are printing out, we can interpolate them. Example: fn main() { println!("My name is {}", "Michael"); } Interpolation We now pass two parameters to the println macro: the first string is "My name is {}". The println macro has special support for the {} braces. It means: see that next parameter? Stick it in here. This program is going to print "My name is Michael". And we separate the parameters by putting a comma. println! Macro The println! macro accepts two parameters: 1. A unique syntax {}, which acts as a placeholder 2. The name of a variable or a constant The variable’s value will be used to replace the placeholder. Example: println!("company rating on level 5:{}",rating_float); The Difference Between Macros and Functions Fundamentally, macros are a way of writing code that writes other code, which is known as metaprogramming. The println! macro expands to produce more code than the code you’ve written manually. The ! character marks this as a macro invocation, not a function call. Metaprogramming is useful for reducing the amount of code you have to write and maintain, which is also one of the roles of functions. However, macros have some additional powers that functions don’t. A function signature must declare the number and type of parameters the function has. Macros, on the other hand, can take a variable number of parameters: we can call println!("hello") with one argument or println!("hello {}", name) with two arguments. 18 The Difference Between Macros and Functions Also, macros are expanded before the compiler interprets the meaning of the code, so a macro can, for example, implement a trait on a given type. A function can’t, because it gets called at runtime and a trait needs to be implemented at compile time. The downside to implementing a macro instead of a function is that macro definitions are more complex than function definitions because you’re writing Rust code that writes Rust code. Due to this indirection, macro definitions are generally more difficult to read, understand, and maintain than function definitions. Another important difference between macros and functions is that you must define macros or bring them into scope before you call them in a file, as opposed to functions you can define anywhere and call anywhere. 19 ANATOMY OF RUST Comments Comments are a way for you to add in some kind of message for other programmers who are reading it. Just like in C, there are two ways to write comments in Rust. The first is to use two forward slashes //. Then, everything up until the end of the line is ignored by the compiler. For example: fn main() { // This line is entirely ignored println!("Hello, world!"); // This printed a message // All done, bye! } Comments The other way is to use a pair of. The advantage of this kind of comment is that it allows you to put comments in the middle of a line of code, and makes it easy to write multi-line comments. The downside is that for lots of common cases, you have to type in more characters than just //. Example: fn main() { println!("Hello, world!" ); } Expressions, Values, and Types Values can be something like the number 9, true, the words "Hello world", and more. Each and every value we produce will have a type. The way we produce values in Rust is with expressions. An expression is a recipe we give to the computer for how to produce a value. The process of turning an expression into a value is called evaluation. Effects Versus Results An effect is something that is caused when you evaluate an expression. The effect of the macro call println! is that it causes output to be printed to the screen. The expression 2 + 3 has no effect. Instead, it has a result. Every expression must evaluate to a value. So the println! macro call must produce a result. The thing is, it doesn’t have anything useful to produce. Variables In Rust Just like in C, a variable is a named storage location that programs may access. A variable is a type of data structure that allows programs to store values. In Rust, variables are always linked with a specific data type. The data type dictates both the variable’s memory size and layout, the range of values stored inside that memory, and the set of operations on the variable. Variable Naming Rules A variable’s name can be letters, numbers, and the underscore character. It starts with a letter or an underscore. Because Rust is case-sensitive, upper- and lowercase letters are separate. Rust code uses snake case as the conventional style for function and variable names. In snake case, all letters are lowercase and underscore separate words. The Rust compiler gives a warning if a declared variable is not used. If you want to have ownership of variables that are not in use just make the variable names start with _(underscore). Because Rust compiler treats variables that begin with _ as special, there is no compiler warning if you don't use those variables. Variable Naming Syntax When declaring a variable in Rust, the data type is optional. The value assigned to the variable determines the data type. The syntax for defining variables is as follows: – let variable_name = value; // no type-specified – let variable_name:dataType = value; //type-specified Example: fn main() { let fees=35000; let salary:f64=45000.00; println!("fees is {} and salary is {}",fees,salary); } Number Separator To make huge numbers easier to read, we may add a visual separator underscore to separate digits. This is 50,000, which can be written as 50_000. Example: let int_with_separator=50_000; Data Types In Rust The Rust Type System represents the language’s many different types of values. The Type System checks the provided values before the software stores or manipulates them. This ensures that the code functions correctly. Greater code hinting and automatic documentation are also possible with the Type System. Rust is a statically typed programming language. In Rust, each value has its own data type. Based on the value provided to the variable, the compiler may automatically determine its data type. Rust Scalar Types A scalar type is a value that has just one value. For instance: 10, 3.14, ’c’ Rust has four distinct scalar types. 1. Integer 2. Floating point 3. Booleans 4. Characters Declaring Variables To declare a variable, we use the let keyword. Example: fn main() { let company_string="Amazon"; // string type let rating_float=3.5; // float type let is_growing_boolean=true; // boolean type let icon_char=‘ ’; //unicode character type println!("company name:{}",company_string); println!("company rating on 5:{}",rating_float); println!("company is growing :{}",is_growing_boolean); println!("company icon:{}",icon_char); } The data type of the variables in the example will deduce from the values assigned to them. Rust, for instance, will assign the string data type to the variable company string, the float data type to rating float, and so on. Unit Rust has a special type for when a variable does not have a value It’s called unit, and it’s written in Rust as a pair of parentheses (). This is for both the type and the value unit. Let’s see what this looks like in code: fn main() { let x: () = (); let y: () = println!("Hello, world!"); assert_eq!(x, y); println!("All units are the same!"); } () is a unit literal, and can be placed on the right hand side of the equal sign, which is how we define the x variable. We also give x an explicit type with : (). See: () works for both the type and the value. Unit Next, we define y as the result of the println macro call. This immediately evaluates the macro, produces the output, generates the unit result value (none), and stores it in y. The next line is a new macro, assert_eq!, which checks to make sure two values are the same. Here’s how it works: – If the two values you pass to assert_eq are the same, nothing happens. – If they’re different, your program exits with something called a panic. To sum up: every expression produces a value. But if it doesn’t have anything useful to produce, it produces unit (). Immutable By default, variables are immutable in Rust. In other words, the value of the variable cannot change once a value is bound to a variable name. Example: fn main() { let fees=25_000; println!("fees is {} ",fees); fees=35_000; println!("fees changed is {}",fees); } Note we cannot set values to the immutable variable fees twice. This is just one of the numerous ways Rust allows programmers to write code while benefiting from the safety and ease of concurrency Let Without Assignment We’ve always immediately assigned to variables with let. However, it’s valid in Rust to separate these steps out. Example: fn main() { let x; x = 5; println!("x == {}", x); let mut y; y = 5; y += 1; println!("y == {}", y); } x is an immutable variable but we’re allowed to say x = 5. The rule is: if you have an immutable variable, you can only assign to it once. Usually that will be at the same time as you use let, but it can be later. Mutable By default, variables are immutable. To make a variable changeable, prefix it with the term mut. A mutable variable’s value can be changed. The syntax for defining a mutable variable is: – let mut variable_name = value; – let mut variable_name:dataType = value; Example: fn main() { let mut fees:i32=35_000; println!("fees is {} ",fees); fees=45_000; println!("fees changed {}",fees); } Number Types In Rust There’s a little bit more to numbers to talk about. The first thing is about integers versus floating point. Just as in C, integers are whole numbers, like 5, -2, 0, etc. They are numbers that don’t have a decimal point or a fractional part. Floating point numbers can have decimal points. Math In Rust + : addition - : subtraction * : multiplication / : division % : Modulus/Remainder (5.0/3.0).floor() : floor division i32::pow(self, exp) : raise self to exp (u32) and return an integer (i32) f32::powi(self,exp) : raise self to exp (i32) and return a float (f32) f32::powf(self,exp) : raise self to exp (f32) and return a float (f32) 38 Math In Rust Unlike C, Rust does NOT support "++" or "--" – Preincrement and postincrement (and the decrement equivalents), while convenient, are also fairly complex. – They require knowledge of evaluation order, and often lead to subtle bugs and undefined behavior in C. – x = x + 1 or x += 1 is only slightly longer, but is unambiguous. Rust does support the following: – x += 1 – x -= 1 – x *= 1 – x /= 1 39 Integers In Rust Within integers, there are signed and unsigned. Signed integers can have a negative sign, and can be positive or negative. Unsigned, cannot have a negative sign, and must be 0 or greater. And finally, there’s the size of an integer, measured in terms of bits. If you have an 8-bit unsigned integer, you can have the numbers 0 through 255. An 8-bit signed integer, on the other hand, can have the numbers -128 to 127. It’s the same total count (256 possibilities), but one includes negatives, the other doesn’t. Integers In Rust In Rust, the way we talk about the types of these integers is i8 (for signed 8-bit integer), u32 (for unsigned 32-bit integer), etc. You can use 8, 16, 32, and 64. There’s also some support for 128, but we’ll almost never need it. There’s also a special isize and usize, which means "use whatever size the computer I’m on likes." This is usually 64. Rust Integers An integer’s size can be arch and the variable is declared as being isize / usize. This indicates that the machine’s architecture will determine the size of the data type. An integer of size arch is 32 bits on an x86 machine and 64 bits on an x64 system. An arch integer is often used to index some type of collection. Example: fn main() { let result=20; // i32 by default let age:u32=30; let sum:i32=5-25; let mark:isize=20; let count:usize=40; println!("result value is {}",result); println!("sum {} and age {}",sum,age); println!("mark {} and count {}",mark,count); } Integer Range Each signed variation may hold integers ranging from -(2(n-1))to 2(n-1)-1, where n is the number of bits used. For example, i8 may hold values ranging from -(27) to 27-1; in this case, we replaced n with 8. Each unsigned variation may hold numbers ranging from 0 to (2n)-1. For example, u8 can hold integers ranging from 0 to (28)-1, or 0 to 255 Integer Overflow An integer overflow happens when the value assigned to an integer variable exceeds the data type’s Rust-specified range. Example: fn main() { let age:u8=255; // 0 to 255 only allowed for u8 let weight:u8=256; //the overflow value is 0 let height:u8=257; //the overflow value is 1 let score:u8=258; //the overflow value is 2 println!("age {} ",age); println!("weight {}",weight); println!("height {}",height); println!("score {}",score); } Integer Overflow The unsigned u8 variable has a permitted range of 0–255. The variables in the above example have values larger than 255 (upper limit for an integer variable in the Rust). When the preceding code is executed, it will produce a warning literal out of range for u8 for the weight, height, and score variables. After 255, the overflow values will begin with 0, 1, 2, and so on. Float Data Type In Rust, float data types are categorized as f32 and f64. The f32 type is a single-precision float, whereas the f64 type is a double- precision float. The type that is used by default is f64. Example: fn main() { let result=20.00; let interest:f32=8.35; let cost:f64=16000.600; // double precision println!("result value {}",result); println!("interest {}",interest); println!("cost {}",cost); } Printing Floats Just like in C, float (and integer) variables can be formatted as they are being printed. You have control over the size of the window that the number is printed in and the number of digits that will be displayed after the decimal point. You do NOT have the ability to have a comma printed after every three characters. Example: println!("The value is {0:12.2}",707.126456789); 47 Character Type Rust’s character data type accepts integers, alphabets, Unicode, and special characters. To declare a variable of the character data type, use the char keyword. The char type in Rust represents a Unicode Scalar Value, which implies it may represent much more than simply ASCII. The Unicode Scalar Values span from U+0000 to U+D7FF [55,295] and from U+E000 [57,344] to U+10FFFF [1,114,111]. Example: fn main() { let special_character=‘@’; //default let alphabet:char=‘D’; let emoji:char=‘ ’; println!("special character {}",special_character); println!("alphabet {}",alphabet); println!("emoji {}",emoji); } An Introduction To The C/Rust Programming Languages Lesson #18: Attributes, Blocks, Strings, Constants, Booleans, Assertions 49 Attributes Attributes are an open-ended system for marking functions and other declarations with extra information They’re used to control Rust compiler warnings and code style checks, include code conditionally, tell Rust how to interact with code written in other languages, and much else. Example: #[allow(unused_variables)] Prevents Rust from printing a warning message about variables that are declared and then never used. 50 Attributes Code like #[derive(Debug)] at the beginning of a Rust program is called an attribute. These attributes are small pieces of code that give information to the compiler. They are not easy to create, but they are very easy to use. If you write an attribute with just # then it will affect the code on the next line. But if you write it with #! then it will affect everything in its own space. Here are some attributes you will see a lot: #[allow(dead_code)] and #[allow(unused_variables)] and #[allow(non_snake_case)] If you write code that you don't use, Rust will still compile but it will let you know. 51 Attributes Declarations can be annotated with ‘attributes’ in Rust. They look like this: #[test] or like this: #![test] The difference between the two is the !, which changes what the attribute applies to: #[foo] struct Foo; mod bar { #![bar] } The #[foo] attribute applies to the next item, which is the struct declaration. The #![bar] attribute applies to the item enclosing it, which is the mod declaration. Otherwise, they’re the same. 52 Attributes Both change the meaning of the item they’re attached to somehow. For example, consider a function like this: #[test] fn check() { assert_eq!(2, 1 + 1); } It is marked with #[test]. This means it’s special: when you run tests, this function will execute. When you compile as usual, it won’t even be included. This function is now a test function. 53 Blocks And Statements Let’s revisit the Hello World program and build up from there: fn main() { println!("Hello, world!"); } The curly braces part is called a block. We’re not limited to just one println! macro call. We can have as many as we like: fn main() { println!("Hello, world!"); println!("Still alive!"); println!("I'm tired, good night!"); } Blocks Example: fn main() { let x: i32 = { 4 + 5 }; println!("4 + 5 == {}", x); } We’ve created a new block, and put the expression 4 + 5 inside of it. Blocks themselves are expressions, and evaluating them evaluates the expression inside. So x ends up holding the value 9. Block Problem Example: fn main() { let x: i32 = { println!("I'm inside a block"); 4 + 5; }; println!("4 + 5 == {}", x); } The resulting i32 value 9 that was produced by evaluating 4 + 5 is thrown away. Since there’s no other expression afterwards, the entire block ends up producing the dummy "nothing interesting" value unit (). But we told the computer that our x value will hold an i32 value, so it complains that the types don’t match up. Blocks & Statements Each of those expressions ending with a semicolon is known in Rust as a statement. And a block is made up of 0 or more of these statements, followed by 0 or 1 expressions. When you evaluate a block, it will evaluate all of the statements in order. Then, if there is no expression at the end, it will produce a unit value. If you end your block with an expression without a semicolon, it will produce that value, otherwise it produces unit. If there is an expression at the end, it will evaluate that expression and produce that value as the block’s result (4+5). Number Variables In Rust Example #1: fn main() { let apples = 10; println!("I have {} apples", apples); } Example #2: fn main() { let apples = 10 + 5; println!("I have {} apples", apples); } Use Of Multiple Variables Example: fn main() { let x = 5; let y = 10; let name = "Michael"; println!("My name is {}, and the answer is {}", name, x + y); } Reuse Of Variable Names Example: fn main() { let x = 5 + 3; let x = x * 2; let x = x - 6; let x = x / 2; println!("The answer is {}", x); } Each time we reuse a variable name, we are creating a brand new variable, not modifying the previous one. We call this shadowing: the new variable with the same name shadows the old variable, so you can’t access the old one anymore. Shadowing In Rust, programmers can specify variables with the same name. In this scenario, the new variable takes precedence over the prior variable. Example: fn main() { let salary=110.00; let salary=2.50; // reads the second salary println!("Value of salary :{}",salary); } This code declares two variables called salary. The first declaration is given a value of 110.00, whereas the second declaration is given a value of 2.50. While displaying output, the second variable shadows or hides the first variable. Constants, unlike variables, cannot be shadowed. Types In Rust Rust has something called type inference, which means that in many cases, you can skip putting in the type, and the compiler will figure it out for you. Sometimes though, we like to write the type out. It can make it easier to read code sometimes, and can help the compiler catch our mistakes better. But usually with simple things, we just skip it. String And Str Rust has more than one type for strings, namely String and &str. &str is called "string slice" The difference comes down to ownership and memory. String allocates memory on the heap and &str does not. Prefer &str as a function parameter or if you want a read-only view of a string; String when you want to own and mutate a string. 63 String Types In Rust In Rust, every value has some type which tells you what kind of thing it is. When you have a string literal (something between double quotes) like "Hello, world!", its type is &str. When you use let to declare a variable, you can give its type as well, like this: fn main() { let name: &str = "Michael"; println!("My name is {}", name); } String Variables In Rust Example: fn main() { let name = "Michael"; println!("My name is {}", name); } name is pointing at the string "Michael", and we can refer to that when we call println!. There’s no "left double quote" and "right double quote," but there are two double quotes which form a pair. Note that in this case we skipped using the "&str" type definition When a string is created, it will be placed on the heap and it will NOT by null terminated (like strings in C are). String String is an owned type that needs to be allocated. It has dynamic size and hence its size is unknown at compile time, since the capacity of the internal array can change at any time. The definition of String contains a Vec so we know that it has a pointer to a chunk of memory, a size, and a capacity. The size gives us the length of the string and the capacity tells us how much long it can get before we need to reallocate. The great thing about String is that it’s very flexible in terms of usage. We can always create new, dynamic strings with it and mutate them. This comes at a cost, of course, in that we need to always allocate new memory to create them. 66 String Operations Unlike C, Strings can be compared using == and !=: let str1 = "Hello"; let str2 = "World"; assert!(str1 != str2); 67 Creating Strings You can create an empty string using: let hello = String::new(); You can create a String from a literal string with String::from: let hello = String::from("Hello, world!"); The length of a string is determined using the len method: printfln!("String length = {}",hello.len()); The difference between a String and a &str is that String is the dynamic heap string type (mutable) , use it when you need to own or modify your string data. &str is an immutable sequence of UTF-8 bytes somewhere in memory. 68 Creating Strings You can append a char to a String with the push method, and append a &str to a String with the push_str method: let mut hello = String::from("Hello, "); hello.push('w'); hello.push_str("orld!"); 69 &String The &String type is simply a reference to a String. This means that this isn’t an owned type and its size is known at compile- time, since it’s only a pointer to an actual String. There isn’t much to say about &String that hasn’t been said about String already; just that since it’s not an owned type, we can pass it around, as long as the thing we’re referencing doesn’t go out of scope and we don’t need to worry about allocations. Mostly, the implications between String and &String comes down to references and borrowing. 70 &str Since &str consists just of a pointer into memory (as well as a size), its size is known at compile time. The memory can be on the heap, the stack, or static directly from the executable. It’s not an owned type, but rather a read-only reference to a string slice. Rust actually guarantees that while the &str is in scope, the underlying memory does not change, even across threads. Its best used when a slice (view) of a string is needed, which does not need to be changed. However, keep in mind that &str is just a pointer to a str, which has an unknown size at compile time, but is not dynamic in nature, so its capacity can’t be changed. This is important to know, but the memory the &str points to can not be changed while the &str is in existence, even by the owner of the str. 71 &str You can use.to_string() to convert a &str to String: let course = "firetruck".tostring(); The reason that you might do this is that once it has become a String, you can modify it. 72 An Introduction To The C/Rust Programming Languages Lesson #19: Tuples, User Input, Files, Operators, If, Else, Match 73 Compound Types In Rust Compound types can combine multiple values into a single type. Tuples and arrays are the two primitive compound types in Rust. Tuples In Rust A tuple is a form of compound data. A scalar type can only store one type of data. An i32 variable, for example, may only store a single integer value. In compound types, we can store multiple values at once, and they can be of various types. Tuple Type A tuple is a generic means of combining several items of various kinds into one compound type – tuples do NOT exist in C. Tuples have a set length: they cannot be increased or decreased in size once created. A tuple is formed by putting a comma-separated list of values within parentheses. Each place in the tuple has a type, and the types of the tuple’s distinct values do not have to be the same. Tuples In Rust There are two different ways to create tuples: //Syntax-1 let tuple_names:(data_type1,data_type2,data_type3)= (value1,value2,value3); //Syntax-2 let tuple_names = (value1,value2,value3); Example: fn main() { let tuples:(i32,f64,u8)=(-326,4.8,23); println!(“{:?}”,tuples); } The println!(“{}”,tuple) syntax cannot be used to show all of the tuple values. This is because a tuple is a compound type. To print values in a tuple, use the println!(“{:?}”, tuple name) syntax. :? println!(“{:?}”, tuple name) {...} surrounds all formatting directives. : separates the name or ordinal of the thing being formatted (which in this case is omitted [nothing comes before it], and thus means "the next thing") from the formatting options. The ? is a formatting option that triggers the use of the std::fmt::Debug implementation of the thing being formatted, as opposed to the default Display trait. fmt::Display implementations assert that the type can be faithfully represented as a UTF-8 string at all times. It is not expected that all types implement the Display trait. fmt::Debug implementations should be implemented for all public types. Output will typically represent the internal state as faithfully as possible. The purpose of the Debug trait is to facilitate debugging Rust code. 78 Tuples In Rust Example: This example prints each value in a tuple. fn main() { let tuple:(i32,f64,u8)=(-326,4.8,23); println!(“integer :{:?}”,tuple.0); println!(“float :{:?}”,tuple.1); println!(“unsigned integer :{:?}”,tuple.2); } Tuple Type Because a tuple is considered as a single compound element, in the next example the variable tup binds to the entire tuple. To extract individual values from a tuple, we may use pattern matching to destructure a tuple value, Example: fn main() { let tup=(600, 7.4, 2); let (a, b, c)=tup; println!("The value of b is: {}", b); } This program begins by creating a tuple and assigning it to the variable tup. It then employs a pattern to divide tup into three distinct variables: a, b, and c. This is referred to as destructuring since it divides one tuple into three pieces. Finally, the program outputs b’s value, which is 7.4. Tuple Type In addition to pattern matching, we can use a period (.) followed by the index of the value we want to retrieve to directly access a tuple element. The initial index of a tuple is 0. Example: fn main() { let x: (i32, f64, u8)=(600, 7.4, 1); let six_hundred=x.0; let seven_point_four=x.1; let one=x.2; } This program generates a tuple, x, and then creates new variables for each element based on their indices. Tuple Type The tuple with no values, (), is a peculiar type with just one value, which is alternatively represented as (). The type is known as the unit type, while the value is known as the unit value. If an expression does not return any other value, it returns the unit value implicitly. Changing Tuples By default, Tuples are immutable. To make a Tuple that can be changed you have to include the "mut" command: let mut tuples:(i32,f64,u8)=(-326,4.8,23); One this has been done, a Tuple can be changed: tuples.0 = 100; 83 Decision Making In Rust Rust If Statement Before executing a piece of code, the if…else construct evaluates a condition. Example: if boolean_expression { // statement will execute if boolean expression istrue } If Boolean expression returns true, the code within the if statement is performed. If the Boolean expression returns false, the first code set after the conclusion of the if statement (after the closing curly brace) is performed. CONDITIONALS Conditionals Example: fn main() { let is_hot = true; if is_hot { println!("It's hot!"); } } Conditionals We can put more complex expression in our if condition if we want: fn main() { let temp = 20; if temp > 15 && temp < 27 { println!("It's fairly comfortable in here!"); } } And the body of the if expression is a block, which conforms with all of the normal rules of a block in Rust. We can put in multiple statements: fn main() { let temp = 20; if temp > 15 && temp < 27 { println!("It's fairly comfortable in here!"); println!("Would you like a drink?"); } } Conditionals We can even put ifs inside other ifs: fn main() { let temp = 20; if temp > 15 && temp < 27 { println!("It's fairly comfortable in here!"); if temp < 20 { println!("But it could be a bit warmer."); } } } There is one restriction in the kind of if expressions we’ve seen so far. They must evaluate to unit () – in other words, the code that runs when the if statement is true does NOT return any value. A Note On Semicolons And Ifs We said that any expression can be converted into an expression statement by putting a semicolon at the end. If you look at the if examples, we never put a semicolon after the if expression. Technically, you can put them there if you want, but that’s not recommended style in Rust. Else This "do this or do this" concept is pretty common, and so we have something built in: else. Each if can be followed by an else, like this: fn main() { let is_hot = false; if is_hot { println!("It's hot!"); } else { println!("It's not hot!"); } } Else If In situations where we will be doing multiple checks, we can use nested If/else statements. However, due to indents, this code will quickly become hard to read and understand. There is a better way: else if. Example: fn tell_temperature(temp: i32) { if temp