CIS 198: Intro to COBOL

00. CIS 198: Intro to COBOL - 图1


CIS 198: Intro to COBOL

  • Designed in 1959 (57 years ago!)
  • We will be using the COBOL2014 standard.
1. IDENTIFICATION DIVISION.
2. PROGRAM-ID. hello-world.
3. PROCEDURE DIVISION.
4. DISPLAY "Hello, world!"
5. .

CIS 198: Rust Programming

00. CIS 198: Intro to COBOL - 图2


Lecture 00: Hello, Rust!

00. CIS 198: Intro to COBOL - 图3

This lecture online:
GitHub One-Page View • Slide View

Overview

“Rust is a systems programming language that runs blazingly fast, prevents nearly all segfaults, and guarantees thread safety.” – rust-lang.org


What is Rust?

Rust is:

  • Fast
  • Safe
  • Functional
  • Zero-cost

Fast

  • Rust compiles to native code
  • Rust has no garbage collector
  • Most abstractions have zero cost
  • Fine-grained control over lots of things
  • Pay for exactly what you need…
  • …and pay for most of it at compile time

Safe

  • No null
  • No uninitialized memory
  • No dangling pointers
  • No double free errors
  • No manual memory management!

Functional

  • First-class functions
  • Trait-based generics
  • Algebraic datatypes
  • Pattern matching

Zero-Cost 100% Safe Abstractions

  • Rust’s defining feature
  • Strict compile-time checks remove need for runtime
  • Big concept: Ownership

Release Model

  • Rust has a new stable release every six weeks
  • Nightly builds are available, well, nightly
  • Current stable: Rust 1.5
  • Rust 1.6 will be out tomorrow (1/21)!
  • This is the version we’ll be using in this class
  • Train model:
Date Stable Beta Nightly
2015-12-10 🚂 1.5 🚆 1.6 🚝 1.7
2016-01-21 🚆 1.6 🚝 1.7 🚈 1.8
2016-03-03 🚝 1.7 🚈 1.8 🚅 1.9

Development


Who Uses Rust?

00. CIS 198: Intro to COBOL - 图4


Big Rust Projects


Administrivia

  • 8-9 homeworks (50%), final project (40%) (may change)
  • Participation (10%)
  • Weekly Rust lecture: Wed. 4:30-6:00pm, Towne 321
  • Mini-Course lecture: Tue. 6:00-7:30pm, Berger Auditorium
  • Piazza
  • We will be using Piazza for announcements; make sure you have gotten emails!
  • Consult the website for the schedule, slides, and homework.
  • Class source material generally hosted on GitHub.
  • Corrections welcome via pull request/issue!
  • Course is in development - give us feedback!

Administrivia: Office Hours

  • David - Mon 4:30-6:00pm
  • Terry - Tues 6:00-7:30pm
  • Kai - Weds 6:00-7:00pm

Office hours held in the Levine 6th floor lounge.

Any changes will be announced. Check the website or Google calendar for the up-to-date schedule.


Administrivia: Homeworks (50%)

  • 8-9 homeworks.
  • Released on Wednesdays and (usually) due the following Wednesday night at midnight.
  • We will be using Classroom for GitHub.
  • Click the link to make a private repo for every homework, which will be your submission.
  • Students start the semester with a total of 5 late days, which provide an extra 24 hours each. You may use up to 2 late days on an assignment.
  • If (and only if) you submit an assignment more than 2 days late or are out of late days, there is a 20% penalty per day beyond the late day extension.


Let’s Dive In!

Hello, Rust!

1. fn main() {
2. println!("Hello, CIS 198!");
3. }
  • All code blocks have links to the Rust playpen so you can run them!

Basic Rust Syntax


Variable Bindings

  • Variables are bound with let:

1. let x = 17; - Bindings are implicitly-typed: the compiler infers based on context. - The compiler can’t always determine the type of a variable, so sometimes you have to add type annotations.

1. let x: i16 = 17; - Variables are inherently immutable:

1. let x = 5; 2. x += 1; // error: re-assignment of immutable variable x 3. let mut y = 5; 4. y += 1; // OK!


Variable Bindings

  • Bindings may be shadowed:

1. let x = 17; 2. let y = 53; 3. let x = "Shadowed!"; 4. // x is not mutable, but we're able to re-bind it - The shadowed binding for x above lasts until it goes out of scope. - Above, we’ve effectively lost the first binding, since both xs are in the same scope. - Patterns may also be used to declare variables:

1. let (a, b) = ("foo", 12);


Expressions

  • (Almost!) everything is an expression: something which returns a value.
  • Exception: variable bindings are not expressions.
  • The “nothing” type is called “unit”, which is written ().
  • The type () has only one value: ().
  • () is the default return type.
  • Discard an expression’s value by appending a semicolon. Now it returns ().
  • Hence, if a function ends in a semicolon, it returns ().
1. fn foo() -> i32 { 5 }
2. fn bar() -> () { () }
3. fn baz() -> () { 5; }
4. fn qux()       { 5; }

Expressions

  • Because everything is an expression, we can bind many things to variable names:

1. let x = -5; 2. let y = if x > 0 { "greater" } else { "less" }; 3. println!("x = {} is {} than zero", x, y); - Aside: "{}" is Rust’s (most basic) string interpolation operator

  • Similar to Python, Ruby, C#, and others; like printf‘s "%s" in C/C++.

Comments

1. /// Triple-slash comments are docstring comments.
2. ///
3. /// `rustdoc` uses docstring comments to generate
4. /// documentation, and supports **Markdown** formatting.
5. fn foo() {
6. // Double-slash comments are normal.

8. /* Block comments
9. * also exist /* and can be nested! */
10. */
11. }

Types


Primitive Types

  • bool: spelled true and false.
  • char: spelled like 'c' or '😺' (chars are Unicode!).
  • Numerics: specify the signedness and size.

  • i8, i16, i32, i64, isize

  • u8, u16, u32, u64, usize
  • f32, f64
  • isize & usize are the size of pointers (and therefore have machine-dependent size)
  • Literals are spelled like 10i8, 10u16, 10.0f32, 10usize.
  • Type inference for non-specific literals default to i32 or f64:
    • e.g. 10 defaults to i32, 10.0 defaults to f64.
  • Arrays, slices, str, tuples.
  • Functions.

Arrays

  • Arrays are generically of type [T; N].
  • N is a compile-time constant. Arrays cannot be resized.
  • Array access is bounds-checked at runtime.
  • Arrays are indexed with [] like most other languages:
  • arr[3] gives you the 4th element of arr
1. let arr1 = [1, 2, 3]; // (array of 3 elements)
2. let arr2 = [2; 32];   // (array of 32 `2`s)

Slices

  • Generically of type &[T]
  • A “view” into an array by reference
  • Not created directly, but are borrowed from other variables
  • Mutable or immutable
  • How do you know when a slice is still valid? Coming soon…
1. let arr = [0, 1, 2, 3, 4, 5];
2. let total_slice = &arr;         // Slice all of `arr`
3. let total_slice = &arr[..];     // Same, but more explicit
4. let partial_slice = &arr[2..5]; // [2, 3, 4]

Strings

  • Two types of Rust strings: String and &str.
  • String is a heap-allocated, growable vector of characters.
  • &str is a type¹ that’s used to slice into Strings.
  • String literals like "foo" are of type &str.
1. let s: &str = "galaxy";
2. let s2: String = "galaxy".to_string();
3. let s3: String = String::from("galaxy");
4. let s4: &str = &s3;

¹str is an unsized type, which doesn’t have a compile-time known size, and therefore cannot exist by itself.


Tuples

  • Fixed-size, ordered, heterogeneous lists
  • Index into tuples with foo.0, foo.1, etc.
  • Can be destructured in let bindings
1. let foo: (i32, char, f64) = (72, 'H', 5.1);
2. let (x, y, z) = (72, 'H', 5.1);
3. let (a, b, c) = foo; // a = 72, b = 'H', c = 5.1

Casting

  • Cast between types with as:
1. let x: i32 = 100;
2. let y: u32 = x as u32;
  • Naturally, you can only cast between types that are safe to cast between.
  • No casting [i16; 4] to char! (This is called a “non-scalar” cast)
  • There are unsafe mechanisms to overcome this, if you know what you’re doing.

Vec<T>

  • A standard library type: you don’t need to import anything.
  • A Vec (read “vector”) is a heap-allocated growable array.
  • (cf. Java’s ArrayList, C++’s std::vector, etc.)
  • <T> denotes a generic type.
  • The type of a Vec of i32s is Vec<i32>.
  • Create Vecs with Vec::new() or the vec! macro.
  • Vec::new() is an example of namespacing. new is a function defined for the Vec struct.

Vec<T>

1. // Explicit typing
2. let v0: Vec<i32> = Vec::new();

4. // v1 and v2 are equal
5. let mut v1 = Vec::new();
6. v1.push(1);
7. v1.push(2);
8. v1.push(3);

10. let v2 = vec![1, 2, 3];
1. // v3 and v4 are equal
2. let v3 = vec![0; 4];
3. let v4 = vec![0, 0, 0, 0];

Vec<T>

1. let v2 = vec![1, 2, 3];
2. let x = v2[2]; // 3
  • Like arrays, vectors can be indexed with [].

  • You can’t index a vector with an i32/i64/etc.

  • You must use a usize because usize is guaranteed to be the same size as a pointer.
  • Other integers can be cast to usize:

    1. let i: i8 = 2; 2. let y = v2[i as usize]; - Vectors has an extensive stdlib method list, which can be found at the offical Rust documentation.


References

  • Reference types are written with an &: &i32.
  • References can be taken with & (like C/C++).
  • References can be dereferenced with * (like C/C++).
  • References are guaranteed to be valid.
  • Validity is enforced through compile-time checks!
  • These are not the same as pointers!
  • Reference lifetimes are pretty complex, as we’ll explore later on in the course.
1. let x = 12;
2. let ref_x = &x;
3. println!("{}", *ref_x); // 12

Control Flow


If Statements

1. if x > 0 {
2. 10
3. } else if x == 0 {
4. 0
5. } else {
6. println!("Not greater than zero!");
7. -10
8. }
  • No parens necessary.
  • Entire if statement evaluates to one expression, so every arm must end with an expression of the same type.
  • That type can be unit ():
1. if x <= 0 {
2. println!("Too small!");
3. }

Loops

  • Loops come in three flavors: while, loop, and for.

  • break and continue exist just like in most languages

  • while works just like you’d expect:
1. let mut x = 0;
2. while x < 100 {
3. x += 1;
4. println!("x: {}", x);
5. }

Loops

  • loop is equivalent to while true, a common pattern.
  • Plus, the compiler can make optimizations knowing that it’s infinite.
1. let mut x = 0;
2. loop {
3. x += 1;
4. println!("x: {}", x);
5. }

Loops

  • for is the most different from most C-like languages
  • for loops use an iterator expression:
  • n..m creates an iterator from n to m (exclusive).
  • Some data structures can be used as iterators, like arrays and Vecs.
1. // Loops from 0 to 9.
2. for x in 0..10 {
3. println!("{}", x);
4. }

6. let xs = [0, 1, 2, 3, 4];
7. // Loop through elements in a slice of `xs`.
8. for x in &xs {
9. println!("{}", x);
10. }

Functions

1. fn foo(x: T, y: U, z: V) -> T {
2. // ...
3. }
  • foo is a function that takes three parameters:
  • x of type T
  • y of type U
  • z of type V
  • foo returns a T.
  • Must explicitly define argument and return types.

  • The compiler is actually smart enough to figure this out for you, but Rust’s designers decided it was better practice to force explicit function typing.


Functions

  • The final expression in a function is its return value.
  • Use return for early returns from a function.
1. fn square(n: i32) -> i32 {
2. n * n
3. }

5. fn squareish(n: i32) -> i32 {
6. if n < 5 { return n; }
7. n * n
8. }

10. fn square_bad(n: i32) -> i32 {
11. n * n;
12. }
  • The last one won’t even compile!
  • Why? It ends in a semicolon, so it evaluates to ().

Function Objects

  • Several things can be used as function objects:
  • Function pointers (a reference to a normal function)
  • Closures (covered later)
  • Much more straightforward than C function pointers:
1. let x: fn(i32) -> i32 = square;
  • Can be passed by reference:
1. fn apply_twice(f: &Fn(i32) -> i32, x: i32) -> i32 {
2. f(f(x))
3. }

5. // ...

7. let y = apply_twice(&square, 5);

Macros!

  • Macros are like functions, but they’re named with ! at the end.
  • Can do generally very powerful stuff.
  • They actually generate code at compile time!
  • Call and use macros like functions.
  • You can define your own with macro_rules! macro_name blocks.
  • These are very complicated. More later!
  • Because they’re so powerful, a lot of common utilities are defined as macros.

  • Print stuff out. Yay.
  • Use {} for general string interpolation, and {:?} for debug printing.
  • Some types can only be printed with {:?}, like arrays and Vecs.
1. print!("{}, {}, {}", "foo", 3, true);
2. // => foo, 3, true
3. println!("{:?}, {:?}", "foo", [1, 2, 3]);
4. // => "foo", [1, 2, 3]

format!

  • Uses println!-style string interpolation to create formatted Strings.
1. let fmted = format!("{}, {:x}, {:?}", 12, 155, Some("Hello"));
2. // fmted == "12, 9b, Some("Hello")"

panic!(msg)

  • Exits current task with given message.
  • Don’t do this lightly! It is better to handle and report errors explicitly.
1. if x < 0 {
2. panic!("Oh noes!");
3. }

assert! & assert_eq!

  • assert!(condition) panics if condition is false.
  • assert_eq!(left, right) panics if left != right.
  • Useful for testing and catching illegal conditions.
1. #[test]
2. fn test_something() {
3. let actual = 1 + 2;
4. assert!(actual == 3);
5. assert_eq!(3, actual);
6. }

unreachable!()

  • Used to indicate that some code should not be reached.
  • panic!s when reached.
  • Can be useful to track down unexpected bugs (e.g. optimization bugs).
1. if false {
2. unreachable!();
3. }

unimplemented!()

  • Shorthand for panic!("not yet implemented")
  • You’ll probably see this in your homework a lot!
1. fn sum(x: Vec<i32>) -> i32 {
2. // TODO
3. unimplemented!();
4. }

Match statements

1. let x = 3;

3. match x {
4. 1 => println!("one fish"),  // <- comma required
5. 2 => {
6. println!("two fish");
7. println!("two fish");
8. },  // <- comma optional when using braces
9. _ => println!("no fish for you"), // "otherwise" case
10. }
  • match takes an expression (x) and branches on a list of value => expression statements.
  • The entire match evaluates to one expression.
  • Like if, all arms must evaluate to the same type.
  • _ is commonly used as a catch-all (cf. Haskell, OCaml).

Match statements

1. let x = 3;
2. let y = -3;

4. match (x, y) {
5. (1, 1) => println!("one"),
6. (2, j) => println!("two, {}", j),
7. (_, 3) => println!("three"),
8. (i, j) if i > 5 && j < 0 => println!("On guard!"),
9. (_, _) => println!(":<"),
10. }
  • The matched expression can be any expression (l-value), including tuples and function calls.
  • Matches can bind variables. _ is a throw-away variable name.
  • You must write an exhaustive match in order to compile.
  • Use if-guards to constrain a match to certain conditions.
  • Patterns can get very complex, as we’ll see later.

Rust Environment & Tools


Rustc

  • Rust’s compiler is rustc.
  • Run rustc your_program.rs to compile into an executable your_program.
  • Things like warnings are enabled by default.
  • Read all of the output! It may be verbose but it is very useful.
  • rustc doesn’t need to be called once for each file like in C.
  • The build dependency tree is inferred from module declarations in the Rust code (starting at main.rs or lib.rs).
  • Typically, you’ll instead use cargo, Rust’s package manager and build system.

Cargo

  • Rust’s package manager & build tool
  • Create a new project:
  • cargo new project_name (library)
  • cargo new project_name --bin (executable)
  • Build your project: cargo build
  • Run your tests: cargo test
  • These get tedious to type, so shell alias to your heart’s content, e.g., cargob/cb and cargot/ct
  • Magic, right? How does this work?

Cargo.toml

  • Cargo uses the Cargo.toml file to declare and manage dependencies and project metadata.
  • TOML is a simple format similar to INI.
  • More in your first homework assignments.
1. [package]
2. name = "Rust"
3. version = "0.1.0"
4. authors = ["Ferris <cis198@seas.upenn.edu>"]

6. [dependencies]
7. uuid = "0.1"
8. rand = "0.3"

10. [profile.release]
11. opt-level = 3
12. debug = false

cargo test

  • A test is any function annotated with #[test].
  • cargo test will run all annotated functions in your project.
  • Any function which executes without crashing (panic!ing) succeeds.
  • Use assert! (or assert_eq!) to check conditions (and panic! on failure)
  • You’ll use this in HW01.
1. #[test]
2. fn it_works() {
3. // ...
4. }

cargo check

  • Not available by default!
  • Run cargo install cargo-check to install it.
  • Functionally the same as cargo build, but doesn’t actually generate any code.
  • => Faster!

HW00: Hello Cargo & Hello Rust

  • Due Monday, 2016-01-25, 11:59pm.
  • Install multirust: manages installations of multiple versions of Rust.
  • Similar to rvm, virtualenv.
  • Linux, OS X, Windows (MSYS2)
  • Install 1.5 now if you want (updating is easy: multirust update stable).
  • 1.6 comes out tomorrow! (1/21)
  • Submitting with Classroom for GitHub is as easy as ~~pie~~ pushing to your private repo.

HW01: Finger Exercises

  • Due Wednesday, 2016-01-27, 11:59pm.
  • Introduction to Rust with “finger exercises”. Use this lecture as a resource!
  • Sieve of Eratosthenes, Tower of Hanoi

Next Time

Next time, baby

  • Ownership, references, borrowing
  • Structured data: structs, enums
  • Methods

Some code examples taken from The Rust Programming Language.