C/CPS 506
Comparative Programming Languages Prof. Alex Ufkes
Topic 9: Rust intro & typing
Notice!
Obligatory copyright notice in the age of digital delivery and online classrooms:
The copyright to this original work is held by Alex Ufkes. Students registered in course CCPS 506 can use this material for the purposes of this course but no other use is permitted, and there can be no sale or transfer or use of the work for any other purpose without explicit permission of Alex Ufkes.
© Alex Ufkes, 2020, 2021 2
Course Administration
© Alex Ufkes, 2020, 2021
3
• •
Getting closer! Rust is our last language. Don’t forget about the assignments!
Moving on…
…to imperative.
Rust is an imperative language. However, we’ll see many cool features that remind us of the functional languages we’ve seen.
© Alex Ufkes, 2020, 2021 4
© Alex Ufkes, 2020, 2021 5
Rust History
• Grew out of a personal project by Mozilla employee Graydon Hoare in 2006
• Mozilla began sponsoring the project in 2009
• Officially announced in 2010
• Rust compiler successfully tested in 2011
• Pre-alpha version released in 2012
• Rust 1.0, the first stable release, arrived on May 15, 2015
• Youngest language we’ve seen so far
• Open source
© Alex Ufkes, 2020, 2021
6
IEEE Developer’s Survey 2018
Also #1 in 2017 and 2016!
© Alex Ufkes, 2020, 2021
7
In Industry? Mozilla in collaboration with Samsung
• Parallel web browser engine Dropbox
• Magic Pocket file system, petabyte storage machines Tor (The Onion Router)
• Experimenting with porting to Rust (from C) for safety features.
© Alex Ufkes, 2020, 2021 8
Rust Features
Systems Programming Language:
• In contrast with application programming languages.
• System software includes things like operating systems, utility software, device drivers, compilers, linkers, etc.
• System languages tend to feature more direct access to physical hardware of a given machine.
© Alex Ufkes, 2020, 2021
9
Rust Features
Syntax:
• Similar to C/C++
• Blocks of code delimited by { }
• Familiar control structures supported
(if, else, while, for, etc.)
• Supports pattern matching! (match)
• Need not use return, last expression
creates return value
• Functions largely composed of
expressions
© Alex Ufkes, 2020, 2021
10
Rust Features Memory Safety:
© Alex Ufkes, 2020, 2021
11
• •
Rust is designed to be memory safe
Null or dangling pointers are not permitted.
“Null or dangling pointers are not permitted”
• In C, we’re allowed to try and access any memory we want.
• This code compiles!
• It produces a run-time error when
we try and index into pointer x.
• Overrunning array bounds does not
necessarily give a run time error!
• Very unsafe use of memory.
© Alex Ufkes, 2020, 2021 12
“Null or dangling pointers are not permitted”
• Java is safer.
• This code compiles, but always
throws an exception when we
access outside array bounds.
• C/C++ only errors if going out of
bounds accesses memory that your program doesn’t have write permission for.
• Java still allows dangling references.
• nums2 can be created without instantiating its object.
© Alex Ufkes, 2020, 2021 13
Rust Features
Memory Safety:
• Rust is designed to be memory safe
• Null or dangling pointers are not permitted.
• What about linked lists? Null pointers are useful.
• Rust defines an option type, which can be used
to test if a pointer has Some value or None • What does this remind you of?
© Alex Ufkes, 2020, 2021
14
Rust Features
Memory Management:
• Rust does not do garbage collection
• Resource acquisition is initialization
• RAII – Originated in C++
• Constructor used to acquire and initialize objects
• Resource deallocation is done by the destructor.
• No valid reference to object == no object.
• Not so in Java! Up to garbage collector.
© Alex Ufkes, 2020, 2021
15
Rust Features
Types and Polymorphism:
• Type system supports mechanism called “traits”
• Directly inspired by Haskell’s type classes
• Supports type inference for variables
declared with let keyword.
• Compile error if inference fails.
• Keyword mut for mutable variables.
© Alex Ufkes, 2020, 2021
16
Rust Features
Pattern Matching:
• Rust supports pattern matching!
• Pattern matching is considered a
sticking point for people learning Rust.
• We already have experience with it
© Alex Ufkes, 2020, 2021
17
Rust & Safety
Strongly, statically typed
• Strong typing means limited implicit type conversions at compile time.
• C is happy to convert between numeric types without issue. Perhaps a compile warning in C++.
• Java raises compile error if there’s a loss of precision (double to float for example).
© Alex Ufkes, 2020, 2021
18
© Alex Ufkes, 2020, 2021 19
© Alex Ufkes, 2020, 2021 20
Rust & Safety
No “Undefined Behavior”
• Null pointer dereferencing
o Attempt to dereference address 0
© Alex Ufkes, 2020, 2021
21
Rust & Safety
No “Undefined Behavior”
• •
Null pointer dereferencing
o Attempt to dereference address 0 Use of variable before it’s initialized o In C, we get whatever was in
memory before that.
o Onlyglobalsauto-initializeto0
© Alex Ufkes, 2020, 2021
22
Rust & Safety
No “Undefined Behavior”
• Null pointer dereferencing
o Attempt to dereference address 0
• Use of variable before it’s initialized o In C, we get whatever was in
memory before that.
o Onlyglobalsauto-initializeto0
• Array index out of bounds
o May or may not cause runtime error
(in C), depends who owns memory
© Alex Ufkes, 2020, 2021
23
© Alex Ufkes, 2020, 2021 24
Rust & Safety
No “Undefined Behavior”
• Signed integer overflow & optimization X+1 > X
• If overflow is undefined, compiler can just optimize this to simply true.
• Dangerous if X can overflow!
• Forcing compiler to consider overflow
means we lose certain optimizations.
© Alex Ufkes, 2020, 2021
25
Rust Non-Goals
• We do not employ any particularly cutting-edge technologies. Old, established techniques are better.
• We do not prize expressiveness, minimalism or elegance above other goals. These are desirable but subordinate goals.
• We do not intend to cover the complete feature-set of C++, or any other language. Rust should provide majority-case features.
• We do not intend to be 100% static, 100% safe, 100% reflective,
or too dogmatic in any other sense. Trade-offs exist.
• We do not demand that Rust run on “every possible platform”.
It must eventually work without unnecessary compromises on widely-used hardware and software platforms.
© Alex Ufkes, 2020, 2021
26
Installing Rust
https://www.rust-lang.org/en-US/index.html
© Alex Ufkes, 2020, 2021 27
Installing Rust
© Alex Ufkes, 2020, 2021
28
Editing Rust Code
Any text editor will do, but I like VSCode:
Visual Studio Code:
• Supports Rust syntax coloring
• Useful for other languages
© Alex Ufkes, 2020, 2021
29
Compiling Rust Code
Command Line – rustc
© Alex Ufkes, 2020, 2021
30
https://www.rustaceans.org/
© Alex Ufkes, 2020, 2021 31
© Alex Ufkes, 2020, 2021 32
Much of the syntax is reminiscent of C/C++
fn main() {
println!(“Hello, world!”);
}
Like C, C++, Java, Haskell, and many others, main() defines the entry point for executing a Rust program.
© Alex Ufkes, 2020, 2021 33
fn main() {
println!(“Hello, world!”);
}
println vs println!
• The ! indicates we’re calling a macro.
• A standard function call doesn’t include !
© Alex Ufkes, 2020, 2021 34
Variables
• By default, Rust variables are immutable
• Once initialized, can’t change.
• Like final or const in other languages
• Declare using let keyword:
fn main() {
let x = 7; println!(“value: {}”, x);
}
© Alex Ufkes, 2020, 2021 35
fn main() {
let x = 7; println!(“value: {}”, x);
}
© Alex Ufkes, 2020, 2021
36
Curly brace pair in a println string acts as a C/C++ style placeholder
Variables
fn main() {
let x = 7;
x = 5;
println!(“value: {}”, x);
}
© Alex Ufkes, 2020, 2021
37
Mutable Variables
Use mut keyword:
fn main() {
let mut x = 7;
x = 5;
println!(“value: {}”, x);
}
• We get a warning, and it’s sensible.
• We change the value of x before the
initial value is ever read. • Pointless.
© Alex Ufkes, 2020, 2021
38
Constant/Global Variables
Rust still has them:
• Use const instead of let
• Always immutable
• Can be declared in global
scope, unlike let
• Must indicate data type (u32)
• More on types coming up.
© Alex Ufkes, 2020, 2021
39
Constant/Global Variables
Can be declared in global scope, unlike let
© Alex Ufkes, 2020, 2021
40
Shadowing
Variables with the same name?
int r = 10;
if (x >= 0) {
BAD
double r = Math.sqrt(x);
}
In Java, variables can have the same name so long as their scope does not overlap:
if (x >= 0) {
double r = Math.sqrt(x); }
else {
float r = 0; }
OK
© Alex Ufkes, 2020, 2021 41
Shadowing
Variables with the same name?
int r = 10; if (x >= 0) {
OK
double r = sqrt(4.0);
}
C++ is less strict. Scopes can overlap, but they can’t be identical:
if (x >= 0) {
double r = sqrt(4.0);
float r = 0; }
BAD
© Alex Ufkes, 2020, 2021
42
Shadowing
Variables with the same name?
© Alex Ufkes, 2020, 2021
43
Shadowing
Variables with the same name?
• What we’re doing here is like re-binding in Haskell or Elixir.
• This doesn’t work with mutable variables.
• Think of this mathematically – We’re simply saying let x = something else.
© Alex Ufkes, 2020, 2021
44
Shadowing VS mut
Why not just use shadowing? Why do we need mut?
• Mutable variables are stuck with their type.
• Can’t assign a value of a different type.
© Alex Ufkes, 2020, 2021
45
Shadowing VS mut
Why not just use shadowing? Why do we need mut?
• With shadowing (rebinding) we can use different types.
• Again, we get a warning because we’re rebinding before
the original binding is ever used.
© Alex Ufkes, 2020, 2021
46
Shadowing VS mut
Why not just use shadowing? Why do we need mut?
• With mut, we’re mutating a variable in memory.
• Storing a different value in the same variable.
• The name still refers to the same place, thus the
type must stay the same.
• With shadowing, we’re getting a new variable in memory each time.
• We’re changing what a given name is referring to.
• We’re not changing the existing value.
© Alex Ufkes, 2020, 2021
47
Data Types
Two subsets: Scalar and Compound
Reminder: Rust is statically typed. Must know all variable types at compile time. Scalar types represent a single value:
• Rust has four: integers, floating-point, Booleans, characters. Compound types group multiple values:
• Two primitive compound types: tuples and arrays.
© Alex Ufkes, 2020, 2021 48
Scalar Types: Integers
Length
Signed
Unsigned
8-bit
i8
u8
16-bit
i16
u16
32-bit
i32
u32
64-bit
i64
u64
arch
isize
usize
• Signed integers are stored using 2s comp
• Arch will be 32 bits on a 32 bit system, 64 bits on a 64 bit system.
• When not specified, Rust defaults to i32
© Alex Ufkes, 2020, 2021
49
Specify Type?
Rust has type inference, but we can be explicit:
© Alex Ufkes, 2020, 2021
50
Integer Literals
In addition to just writing the value…
Notice the _
• This is a handy visual sugar
• Hard to count the zeroes in 1000000000.
What number is this?
• Easy to see 1_000_000_000 is one billion.
Number literals
Example
Decimal
98_222
Hex
0xff
Octal
0o77
Binary
0b1111_0000
Byte (u8 only)
b’A’
Bytes can be character literals
© Alex Ufkes, 2020, 2021
51
Scalar Types: Floating Point
• Two kinds – 32 and 64 bit (float and double, single and double precision)
• Represented using standard IEEE-754
Default
© Alex Ufkes, 2020, 2021 52
Numeric Operations
© Alex Ufkes, 2020, 2021
53
Numeric Operations
© Alex Ufkes, 2020, 2021
54
Mixed Expressions?
Rust doesn’t mess around when it comes to implicit type conversion.
© Alex Ufkes, 2020, 2021
55
Mixed Expressions?
© Alex Ufkes, 2020, 2021
56
Mixed Expressions?
Cast using: as type
• Comments same as Java/C/C++
• Both block and single-line
© Alex Ufkes, 2020, 2021
57
Finally!
Mixed Expressions?
Division may truncate, good reason to avoid implicit conversion…
© Alex Ufkes, 2020, 2021 58
Why?!
• Adding float to int means converting the integer to a floating-point type, then adding.
• CPU doesn’t add different types.
• Float and int arithmetic is done using different
instructions, in different locations on CPU.
• It’s possible to introduce errors in precision!
• An integer in binary is exactly precise.
• The same value represented as a floating point
may lose significant digits.
• Most languages don’t even warn about this –
Rust doesn’t allow it at all.
© Alex Ufkes, 2020, 2021
59
© Alex Ufkes, 2020, 2021 60
Scalar Types: Boolean true, false. Easy:
© Alex Ufkes, 2020, 2021
61
Scalar Types: Characters Rust supports Unicode:
© Alex Ufkes, 2020, 2021
62
Compound Types: Tuples
Tuples can be heterogeneous, and we need not specify type. Rust can infer it.
© Alex Ufkes, 2020, 2021
63
Accessing Elements
De-structuring!
© Alex Ufkes, 2020, 2021
64
Accessing Elements
Can also access directly:
© Alex Ufkes, 2020, 2021
65
Can we go out of bounds?
Accessing Elements
Out of bounds:
© Alex Ufkes, 2020, 2021
66
Compile error in Rust
Accessing Elements
Can we fool it?
© Alex Ufkes, 2020, 2021
67
Nope.
Compound Types
Arrays
Arrays in Rust are: homogeneous, zero-indexed, fixed in size.
© Alex Ufkes, 2020, 2021
68
Accessing Elements
Out of bounds:
Runtime error, much like Java. Prevents out of bounds array accesses.
© Alex Ufkes, 2020, 2021
69
Array of Tuples
Same rules as Haskell:
© Alex Ufkes, 2020, 2021
70
Array of Tuples
Same rules as Haskell: Tuple types must be the same
© Alex Ufkes, 2020, 2021
71
Types & Literals: Summary
4 Scalar types:
Integer – u8, u16, u32, u64, usize, i8, i16, i32, i64, isize Floating Point – f32, f64
Boolean – bool (true, false)
Character – Unicode: ‘Z’, ‘a’, ‘&’, ‘\u{00C5}’, etc
2 Compound types:
Tuple – heterogeneous Arrays – homogeneous
Rust supports other data structures such as strings and vectors. These are not base types, but very useful.
© Alex Ufkes, 2020, 2021 72
Strings
String literals and escape characters are as expected
© Alex Ufkes, 2020, 2021
73
© Alex Ufkes, 2020, 2021 74
Functions
We’ve seen main()
• Returns nothing, accepts no arguments.
• Convention for naming functions is snake_case.
• Words separated by underscores.
© Alex Ufkes, 2020, 2021
75
Functions
Unlike C/C++, Rust doesn’t care about ordering
© Alex Ufkes, 2020, 2021
76
Parameters
identifier: type
• Parameters separated by commas. • Indicatingtypeismandatory
• Nothing too unusual here
© Alex Ufkes, 2020, 2021
77
Careful Now…
© Alex Ufkes, 2020, 2021
78
Statements & Expressions
Rust is primarily expression based, but still has statements.
Two types of statements:
• Declaration statements return nothing
• Expression statements return empty tuple ()
let x = 6; // This is a declaration statement
The above does not return a value. We can’t do the following:
let y = (let x = 6);
© Alex Ufkes, 2020, 2021 79
Statements & Expressions
Rust is primarily expression based, but still has statements.
Two types of statements:
• Declaration statements return nothing
• Expression statements return empty tuple ()
5 + 2; // This is an expression statement
The above expression is evaluated, but the result is ignored (not saved).
© Alex Ufkes, 2020, 2021
80
5 + 2
y = 5+2;
is an expression. It evaluates to 7.
is an expression statement. It returns (), but the result of the nested expression 5+2 is saved to y
Statements & Expressions
let y = (let x = 6);
© Alex Ufkes, 2020, 2021
81
Statements & Expressions
Not OK… but what does this error mean?
© Alex Ufkes, 2020, 2021
82
OK
Statements & Expressions
• Variable y gets re-assigned.
• The expression statement (y=8) returns an empty tuple in Rust.
• Can’t assign an empty tuple to a variable declared to hold i32!
© Alex Ufkes, 2020, 2021 83
Statements & Expressions
© Alex Ufkes, 2020, 2021
84
Here:
• Value of x will be 3 • Value of y will be ()
empty tuple
Statements & Expressions
x + 6
// This is an expression
// This is an expression statement // containing an expression
Declaration statement
x = 5 + 6;
Expression
Expression statement
In fact:
let x = 6;
Expression
© Alex Ufkes, 2020, 2021
85
Scope Blocks as Expressions
Creating a new scope block?
We can do this in Java and C/C++, though again it isn’t so common:
Not a control structure or method, just a block of code with its own scope
© Alex Ufkes, 2020, 2021 86
Scope Blocks as Expressions
Scope blocks like this are expressions in Rust:
There’s a few things going on here:
• We’re trying to bind a value to y.
• Thus, the block { } should evaluate to
something.
• Notice there’s no semicolon after z + 1
• z + 1 is an expression.
• Adding a semi-colon would make it an
expression statement.
• Thus, the block { } would return ().
• Probably not what we want.
© Alex Ufkes, 2020, 2021
87
Scope Blocks as Expressions
Scope blocks like this are expressions in Rust:
expression!
{ let x = 3; x + 1 }
let y =
;
This whole thing is a declaration statement
© Alex Ufkes, 2020, 2021
88
Scope Blocks as Expressions
Scope blocks like this are expressions in Rust:
© Alex Ufkes, 2020, 2021
89
Return Value
Think of functions the same way.
The last line should be an expression – no semi-colon.
-> type
• •
Explicitly indicate return type Result of expression gets returned
© Alex Ufkes, 2020, 2021
90
Return Value
Add semicolon? It becomes expression statement, returns (), type mismatch:
© Alex Ufkes, 2020, 2021 91
Fantastic Rust Reference:
https://doc.rust-lang.org/book/second-edition/
© Alex Ufkes, 2020, 2021 92
© Alex Ufkes, 2020, 2021 93