Rajapinnat ja virheet

  • Versiointi ja lähdekoodi
  • Virheenkäsittely
  • Piirteet
  • Iteraattorit

Semanttinen versiointi

  • Kertoo kirjastojen versioiden yhteensopivuuden
  • MAJOR.MINOR.PATCH
  • Taaksepäinyhteensopivuus (backward compatibility)

semver


Avoin lähdekoodi

  • Ilmaista ja vapaasti saatavilla
  • Lähdekoodia saa jakaa ja siihen saa tehdä muutoksia
  • Kaikki netistä, kuten GitHubista, löytyvä lähdekoodi ei ole avointa
  • Lisenssit: GPL, MIT, Apache, BSD, UNLICENSE
  • Ohjelmisto: Chromium, VSCodium, GNU/Linux, Firefox, GNU Emacs

chromium

linux

GNU

firefox

blender

emacs

vscodium

debian

freeCAD


Moduulit

  • Rust-lähdekoodin voi jakaa tiedostoihin ja kansioihin
    • Jokainen tiedosto ja kansio määrittelee moduulin
  • Moduuleilla erotetaan kirjastojen osat
  • Moduuli koostuu yksityisistä ja julkisista (pub) itemeistä
otarustlings/
├── README.md
├── Cargo.toml
└── src/
    ├── lib/
    │   ├── exercise.rs
    │   ├── lib.rs
    │   ├── menu.rs
    │   ├── state.rs
    │   ├── tester.rs
    │   └── utils.rs
    └── main.rs

Rust lähdekoodi

  • Paketti koostuu laatikoista
    • Cargo.toml
  • Laatikko koostuu moduuleista
  • Laatikko voi olla ohjelma (bin) tai kirjasto (lib)

crates

[package]
name = "otarustlings"
version = "0.4.4"
edition = "2021"

[lib]
name = "otarustlings"
path = "src/lib/lib.rs"

[[bin]]
name = "otarustlings"
path = "src/main.rs"
doc = false

[dependencies]
notify = "4.0.17"
walkdir = "2.3.2"
toml = "0.5.8"

Pakettihierarkia

  • Paketeilla on riippuvuuksia toisten pakettien kirjastoihin (lib)
  • Kääntöaika ∝ pakettien määrään
  • Yhdellä paketilla voi olla yli 100 transienttia riippuvuutta
λ cargo tree
otarustlings v0.4.4
├── notify v4.0.17
│   ├── bitflags v1.3.2
│   ├── inotify v0.7.1
│   │   ├── bitflags v1.3.2
│   │   ├── inotify-sys v0.1.5
│   │   │   └── libc v0.2.124
│   │   └── libc v0.2.124
│   ├── libc v0.2.124
│   ├── mio v0.6.23
│   │   ├── cfg-if v0.1.10
│   │   ├── iovec v0.1.4
│   │   │   └── libc v0.2.124
│   │   ├── libc v0.2.124

Virheenkäsittely

  • Bugi, Virhe, Result
  • Virheenkäsittely on eksplisiittistä ja pakollista
  • Paniikki panic!, unwrap
  • Propagaatio ?
fn read_hello() -> Result<String, io::Error> {
    let mut file = File::open("hello.txt")?;
    let mut username = String::new();
    file.read_to_string(&mut username)?;
    Ok(username)
}

Virheenkäsittely Pythonissa

def upper(name):
	if name == "":
        raise Exception("no name")
	return name.upper()

def main():
	try:
        name = upper("sammy")
    except Exception as err:
		print("Could not upper:", err)
		return

	print("Uppercase name:", name)

Virheenkäsittely GO:ssa

func upper(name string) (string, error) {
	if name == "" {
		return "", errors.New("no name")
	}
	return strings.ToTitle(name), nil
}

func main() {
	name, err := upper("sammy")
	if err != nil {
		fmt.Println("Could not upper:", err)
        return
	}

	fmt.Println("Uppercase name:", name)
}

Algebrallinen ratkaisu

Imperatiivisissa kielissä

  • Virheet keskeyttävät funktion suorituksen
  • NULL on mitä tahansa tyyppiä
  • NULLin tarkistaminen ei ole pakotettua
  • Ei ole selvää millä rivillä voi tapahtua virhe

Funktionalisissa kielissä

  • Virheet eivät ohita suoritusjärjestystä
  • NULLia vastaava arvo ei ohita tyyppijärjestelmää
  • Option pitää aina tarkistaa (esim match) jotta saa sisäisen arvon
  • NULL blog

LanguageNULLMaybeNULL Score
Objective Cnil, Nil, NULL, NSNullMaybe, from SVMaybe
CNULL⭐⭐
Gonil⭐⭐
C++NULLboost::optional, from Boost.Optional⭐⭐⭐
PythonNoneMaybe, from PyMonad⭐⭐⭐
Javanulljava.lang.Optional⭐⭐⭐⭐
Scalanullscala.Option⭐⭐⭐⭐
OCamloption⭐⭐⭐⭐⭐
RustOption⭐⭐⭐⭐⭐
HaskellMaybe⭐⭐⭐⭐⭐

Ohjelman (bin) virheenkäsittely

  • Väärän tilan välttäminen
  • Turvallinen kaatuminen
  • Käyttäjäystävällisyys

Kirjaston (lib) virheenkäsittely

  • Väärän tilan välttäminen
  • Turvallinen kaatuminen
  • Virheiden propagointi
  • Totaalit funktiot
  • Koodariystävällinen

Result::<T, E>::unwrap

impl<T, E> Result<T, E> {
    pub fn unwrap(self) -> T
    where
        E: fmt::Debug,
    {
        match self {
            Ok(t) => t,
            Err(e) => panic!(
				"called `Result::unwrap()` on an `Err` value: {:?}",
				e,
			),
        }
    }
}

Geneeriset tyypit

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

type

use std::result;

pub type Result<T> = result::Result<T, ConfigError>;

pub fn config_file(path: impl AsRef<Path>) -> Result<Config> { .. }
  • Tapa nimetä jokin tyyppi, kuten "vakio" tyyppitasolla
  • Geneerinen tyyppi on kuin funktio tyyppitasolla

Piirteet

  • Tyyppien välinen rajapinta
  • Kuvaa toiminnallisuutta abstraktisti
  • Yleisiä piirteitä:
    • Default
    • Clone
    • Iterator
  • Geneeristen tyyppien rajoittaminen
  • dyn Trait

struct String


fn upper

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum UpperError {
    NoName,
}

pub fn upper<T>(name: T) -> Result<String, UpperError>
where
    T: AsRef<str>,
{
    let name = name.as_ref(); // convert to &str
    if name.is_empty() {
        return Err(UpperError::NoName);
    }
    Ok(name.to_uppercase())
}

fn main() -> Result<(), UpperError> {
    let name = upper("sammy")?;
    println!("Uppercase name: {}", name);
    Ok(())
}

trait Iterator

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}
  • Abstrakti rajapinta asioille joilta voi pyytää seuraavan arvon
  • Option ilmaisee onko seuraavaa arvoa
  • Loppumaton laiska iterointi
  • Ei tule sekoittaa IntoIterator piirteen kanssa

// src/core/slice/mod.rs
impl<T> [T] {
    pub fn iter(&self) -> Iter<'_, T> {
        Iter::new(self)
    }
}
  • Mitä tyyppiä on &self?
#![allow(unused)]
fn main() {
let x = &[1, 4];
let mut iterator = x.iter();

assert_eq!(iterator.next(), Some(&1));
assert_eq!(iterator.next(), Some(&4));
assert_eq!(iterator.next(), None);
}
  • Iteraattori palauttaa &T arvoja
  • Miksi mut?

struct Iter (slice)

  • Iterin kenttiä ja implementaatiota ei tarvitse tietää
  • Implementoi Iterator piirteen


Muita Iter-nimisiä tietueita


std::iter::repeat

  • Repeat on Iterator
  • Palauttaa next() kutsuessa kloonin: Some(elt.clone())
pub fn repeat<T>(elt: T) -> Repeat<T>
where
    T: Clone,
#![allow(unused)]
fn main() {
// the number four 4ever:
let mut fours = std::iter::repeat(4);

assert_eq!(Some(4), fours.next());
assert_eq!(Some(4), fours.next());
assert_eq!(Some(4), fours.next());
assert_eq!(Some(4), fours.next());

// yup, still four
assert_eq!(Some(4), fours.next());
}

Iteraattoriadapterit

MetodiKuvaus
mapPalauttaa iteraattorin, jonka alkiot kuvataa λ-funktiolla
filterPalauttaa iteraattorin, jonka alkiot ovat ne jotka λ-funktio kuvaa trueksi
collectKuluttaa iteraattorin ja kerää arvot kokoelmaan
fn is_prime(x: &i32) -> bool { .. }

let a = [1, 2, 3, 4, 5, 6];

let mersenne_primes: Vec<i32> = a
    .iter()
    .map(|&x| 2i32.pow(x) - 1)
    .filter(is_prime)
    .collect();

assert_eq!(mersenne_primes, vec![3, 7, 31]);

Range iteraattori

fn is_prime(x: &i32) -> bool { .. }

let mut mersenne_primes = (1..)
    .map(|x| 2i32.pow(x) - 1)
    .filter(is_prime);

assert_eq!(mersenne_primes.next(), Some(3));
assert_eq!(mersenne_primes.next(), Some(7));
assert_eq!(mersenne_primes.next(), Some(31));
assert_eq!(mersenne_primes.next(), Some(127));
// ...
assert_eq!(mersenne_primes.next(), Some(618970019642690137449562111)

Kotiläksy

  • kirjasta 9.1, 9.2
  • https://desmondwillowbrook.github.io/blog/rust-error-handling/
    • Käy läpi piirteitä, Box<dyn Error>, serde, virheen propagointia


Cargo.toml
[dependencies]
serde_json = "1.0.81"

[dependencies.serde]
version = "1.0.137"
features = ["derive"]
lib.rs
fn read_file(path: &Path) ->
    Result<Vec<u8>, std::io::Error>
{
    let mut f = File::open(&path)?;
    let mut buf = Vec::new();
    f.read_to_end(&mut buf)?;
    Ok(buf)
}

serde

#[derive(Deserialize)]
pub struct Config {
  pub name: String
}

#[derive(Debug)]
pub enum ConfigError {
    FileError(io::Error),
    DeserializeError(serde_json::Error)
}

pub fn config_file(path: impl AsRef<Path>) ->
	Result<Config, ConfigError>
{
    let buf = read_file(path.as_ref())?;
    let conf = serde_json::from_slice(&buf)?;
    Ok(conf)
}

pub fn config_file<P: AsRef<Path>>(path: P) ->
	Result<Config, ConfigError> // ...

#[derive(Debug)]
pub enum ConfigError {
    FileError(io::Error),
    DeserializeError(serde_json::Error),
}

impl From<serde_json::Error> for ConfigError {
    fn from(e: serde_json::Error) -> Self {
        Self::DeserializeError(e)
    }
}

impl From<io::Error> for ConfigError {
    fn from(e: io::Error) -> Self {
        Self::FileError(e)
    }
}

main.rs:

use error_handling::config_file;

fn main () {
	let conf = config_file("config.json").unwrap();
	println!("{}", conf.name);
}

λ cargo run
thread 'main' panicked at
called Result::unwrap() on an Err value:
	FileError(Os {
		code: 2,
		kind: NotFound,
		message: "No such file or directory"
	})
	src/main.rs:4:43
run with RUST_BACKTRACE=1 to display a backtrace
  • tiedostoa config.json ei ole
  • virhe ei kerro tiedoston nimestä

use std::path::PathBuf;
#[derive(Debug)]
pub enum ConfigErrorType {
    FileError(std::io::Error),
    DeserializeError(serde_json::Error),
}

#[derive(Debug)]
pub struct ConfigError { // uusi
    pub path: PathBuf,
    pub err: ConfigErrorType,
}