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 ViewSlide 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.
  7. /* Block comments
  8. * also exist /* and can be nested! */
  9. */
  10. }

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();
  3. // v1 and v2 are equal
  4. let mut v1 = Vec::new();
  5. v1.push(1);
  6. v1.push(2);
  7. v1.push(3);
  8. 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. }
  5. let xs = [0, 1, 2, 3, 4];
  6. // Loop through elements in a slice of `xs`.
  7. for x in &xs {
  8. println!("{}", x);
  9. }

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. }
  4. fn squareish(n: i32) -> i32 {
  5. if n < 5 { return n; }
  6. n * n
  7. }
  8. fn square_bad(n: i32) -> i32 {
  9. n * n;
  10. }
  • 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. }
  4. // ...
  5. 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! & println!

  • 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;
  2. match x {
  3. 1 => println!("one fish"), // <- comma required
  4. 2 => {
  5. println!("two fish");
  6. println!("two fish");
  7. }, // <- comma optional when using braces
  8. _ => println!("no fish for you"), // "otherwise" case
  9. }
  • 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;
  3. match (x, y) {
  4. (1, 1) => println!("one"),
  5. (2, j) => println!("two, {}", j),
  6. (_, 3) => println!("three"),
  7. (i, j) if i > 5 && j < 0 => println!("On guard!"),
  8. (_, _) => println!(":<"),
  9. }
  • 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>"]
  5. [dependencies]
  6. uuid = "0.1"
  7. rand = "0.3"
  8. [profile.release]
  9. opt-level = 3
  10. 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.