Android程序员初学Rust-基本类型

195 阅读16分钟

1.jpg

这是本教程第一天。今天我们将涵盖许多内容:

  • 基本的 Rust 语法:变量、标量和复合类型、枚举、结构体、引用、函数和方法。
  • 类型和类型推断。
  • 控制流结构:循环、条件语句等。
  • 用户定义类型:结构体和枚举。
  • 模式匹配:解构枚举、结构体和数组。

什么是 Rust

Rust 是一门新兴的编程语言,于 2015 年发布了 1.0 版本:

  • Rust 是一种静态编译语言,与 C++ 类似。
    • rustc 使用 LLVM 作为其后端。
  • Rust 支持多种平台和架构:
    • x86, ARM, WebAssembly, …
    • Linux, Mac, Windows, …
  • Rust 被用于各种设备:
    • 固件和引导加载程序
    • 智能显示屏
    • 手机
    • 桌面
    • 服务器

Rust 有什么好处

Rust 的一些独特的地方:

  • 编译时内存安全:所有类型的内存错误在编译时就被阻止了:
    • 没有未初始化的变量。
    • 没有重复释放(double-frees)。
    • 没有释放后使用(use-after-free)。
    • 没有空指针(NULL pointers)。
    • 没有遗忘的已锁定的互斥锁(locked mutexes)。
    • 线程之间没有数据竞争。
    • 没有迭代器失效。
  • 没有未定义的运行时行为Rust 语句的作用永远不会不明确。
    • 数组访问是边界检查的。
    • 整数溢出是定义好的(panicunwrap)。
  • 现代语言特性:与高级语言一样富有表现力和易用性。
    • 枚举和模式匹配。
    • 泛型。
    • 无开销的 FFI(外部函数接口)。
    • 零成本抽象。
    • 优秀的编译器错误信息。
    • 内置的依赖管理器。
    • 内置的测试支持。
    • 优秀的语言服务器协议支持。

Hello World

hello.jpg

好吧,终于到了这里。

我们直接来看可能是最简单的 Rust 程序,一个经典的 “Hello World” 程序:

fn main() {
    println!("Hello 🌍!");
}
// Output
// Hello 🌍!

上面这个简单的例子,我们可以学到:

  • 函数使用 fn 关键字声明。
  • main 函数是程序的入口点。
  • 代码块像 CC++ 一样用花括号括起来。
  • 语句以分号结尾。
  • Rust 有卫生宏,println!就是一个例子。
  • Rust 中的字符串采用 UTF-8 编码,可以包含任何 Unicode 字符。

什么是卫生宏:宏在扩展时不会引入与周围代码环境中的变量名冲突的风险。

变量与值

2.png

Rust 通过静态类型提供类型安全。变量绑定使用 let 关键字来实现:

fn main() {
    let x: i32 = 10;
    println!("x: {x}");
}

// Output
// x: 10

Rust 中,我们不说“赋值”,而是说“绑定”。例如上述代码我们说:把 10 这个 i32 类型的值绑定到变量 x

以下是一些基本的内置类型,以及每种类型的字面量值的语法。

类型字面量
有符号整数i8, i16, i32, i64, i128, isize-10, 0, 1000, 123i64
无符号整数u8, u16, u32, u64, u128, usize0, 123, 10u16
浮点数f32, f643.14, -10.0e20, 2f32
Unicode标量值char'a', 'α', '\u{123}'
布尔值booltrue, false

这些类型的宽度如下:

  • iNuNfNN 位宽。
  • isizeusize 与指针宽度相同。
  • char 是 32 位宽。
  • bool 是 8 位宽 。

如表格中表示的那样,我们可以使用一个类型后缀主动声明变量的类型:

let a = 23i32;
let b = 23i64;
let c = 23f32;

Rust 中的算数运算:

fn interproduct(a: i32, b: i32, c: i32) -> i32 {
    return a * b + b * c + c * a;
}

fn main() {
    println!("result: {}", interproduct(120, 100, 248));
}
// Output
// result: 66560

类型推断:

Rust 将通过变量的使用方式来确定其类型:

fn takes_u32(x: u32) {
    println!("u32: {x}");
}

fn takes_i8(y: i8) {
    println!("i8: {y}");
}

fn main() {
    let x = 10;
    let y = 20;

    takes_u32(x);
    takes_i8(y);
}

// Output
// u32: 10
// i8: 20

当我们定义变量 xy 的时候,我们并没有给予类型,此时 Rust 也不着急确定它们的类型。

在我们第一次使用变量时,Rust 便就确定了它们的类型,xu32yi8。类型推断完成之后,y 就只能当做 i8 用了。此时如果我们后续再次调用 takes_u32(y)Rust 便会提示一个错误:expected u32, found i8

练习 1

斐波那契数列以 [0, 1] 开头。对于 n > 1,第 n 个斐波那契数通过递归计算,即第 n-1 个斐波那契数与第 n-2 个斐波那契数之和。


fn fib(n: u32) -> u32 {
    if n < 2 {
        // The base case.
        return todo!("Implement this");
    } else {
        // The recursive case.
        return todo!("Implement this");
    }
}

fn main() {
    let n = 20;
    println!("fib({n}) = {}", fib(n));
}

控制流

3.jpg

Rust 中,代码块由花括号 {} 括起来,包含一系列表达式。每个代码块都有一个值和一种类型,它们就是代码块中最后一个表达式的值和类型:

fn main() {
    let z = 13;
    let x = {
        let y = 10;
        dbg!(y);
        z - y
    };
    dbg!(x);
}

// Output
// [src/main.rs:5:9] y = 10
// [src/main.rs:8:5] x = 3

如果最后一个表达式以 ; 结尾,那么最终的值和类型就是 ()

变量的作用域仅限于其所在的代码块 。

此处使用了另外一个打印的宏——dbg!。这里只需要记住:dbg!宏是一个非常实用的调试工具,它能够帮助开发者快速打印变量的值及其相关信息,而不会影响原有代码的逻辑。

if

你使用if表达式的方式与在其他语言中使用if语句的方式完全相同:

fn main() {
    let x = 10;
    if x == 0 {
        println!("zero!");
    } else if x < 100 {
        println!("biggish");
    } else {
        println!("huge");
    }
}

// Output
// biggish

此外,你可以将if用作表达式。每个代码块的最后一个表达式将成为if表达式的值:

fn main() {
    let x = 10;
    let size = if x < 20 { "small" } else { "large" };
    println!("number size: {}", size);
}

// Output
// number size: small

match

match 可用于将一个值与一个或多个选项进行匹配检查:

fn main() {
    let val = 1;
    match val {
        1 => println!("one"),
        10 => println!("ten"),
        100 => println!("one hundred"),
        _ => {
            println!("something else");
        }
    }
}

// Output
// one

if 表达式一样,match 也可以返回一个值;

fn main() {
    let flag = true;
    let val = match flag {
        true => 1,
        false => 0,
    };
    println!("The value of {flag} is {val}");
}
// Output
// The value of true is 1

match 应该是 Rust 中最强大的一个控制流了,后续我们会介绍更多关于 match 的用法,这里只做简单介绍

循环

Rust 中有三个循环关键字:whileloopfor

while 关键字的工作方式与其他语言类似,只要条件为真,就会执行循环体:

fn main() {
    let mut x = 200;
    while x >= 10 {
        x = x / 2;
    }
    dbg!(x);
}

// Output
// [src/main.rs:6:5] x = 6

for 循环会遍历值的范围或集合中的元素:

fn main() {
    for x in 1..5 {
        dbg!(x);
    }

    for elem in [2, 4, 8, 16, 32] {
        dbg!(elem);
    }
}

// Output
// [src/main.rs:3:9] x = 1
// [src/main.rs:3:9] x = 2
// [src/main.rs:3:9] x = 3
// [src/main.rs:3:9] x = 4
// [src/main.rs:7:9] elem = 2
// [src/main.rs:7:9] elem = 4
// [src/main.rs:7:9] elem = 8
// [src/main.rs:7:9] elem = 16
// [src/main.rs:7:9] elem = 32

loop 语句会一直循环,直到遇到 break,这个和其他语言中的 while(true) 类似,这个也是 Rust 中比较有特色的一个循环:

fn main() {
    let mut i = 0;
    loop {
        i += 1;
        dbg!(i);
        if i > 100 {
            break;
        }
    }
}

如果你想立即开始下一次迭代,请使用 continue。 如果你想提前退出任何类型的循环,请使用 break

对于 loop 循环,它可以接受一个可选表达式,该表达式将成为循环表达式的值。

fn main() {
    let mut i = 0;
    loop {
        i += 1;
        if i > 5 {
            break;
        }
        if i % 2 == 0 {
            continue;
        }
        dbg!(i);
    }
}

// Output
// [src/main.rs:11:9] i = 1
// [src/main.rs:11:9] i = 3
// [src/main.rs:11:9] i = 5

continuebreak 都可以选择性地接受一个标签参数,用于跳出嵌套循环:

fn main() {
    let s = [[5, 6, 7], [8, 9, 10], [21, 15, 32]];
    let mut elements_searched = 0;
    let target_value = 10;
    'outer: for i in 0..=2 {
        for j in 0..=2 {
            elements_searched += 1;
            if s[i][j] == target_value {
                break 'outer;
            }
        }
    }
    dbg!(elements_searched);
}

// Output
// [src/main.rs:13:5] elements_searched = 6

下面是一个函数的例子,在先前的例子中,我们已经知道函数怎么编写了吧:

fn gcd(a: u32, b: u32) -> u32 {
    if b > 0 {
        gcd(b, a % b)
    } else {
        a
    }
}

fn main() {
    dbg!(gcd(143, 52));
}

// Output
// [src/main.rs:10:5] gcd(143, 52) = 13

宏在编译期间会扩展为 Rust 代码,并且可以接受可变数量的参数。宏通过末尾的 ! 来区分。Rust 标准库包含各种有用的宏:

  • println!(format, ..) 会将一行文本打印到标准输出,并应用 std::fmt 中描述的格式化规则。
  • format!(format, ..) 的工作方式与 println! 类似,但会将结果作为字符串返回。
  • dbg!(expression) 会记录表达式的值并返回该值,同时还会打印文件名,行号信息。
  • todo!() 将一段代码标记为尚未实现。如果执行它,将会导致程序崩溃。
fn factorial(n: u32) -> u32 {
    let mut product = 1;
    for i in 1..=n {
        product *= dbg!(i);
    }
    product
}

fn fizzbuzz(n: u32) -> u32 {
    todo!()
}

fn main() {
    let n = 4;
    println!("{n}! = {}", factorial(n));
}

// Output
// [src/main.rs:4:20] i = 1
// [src/main.rs:4:20] i = 2
// [src/main.rs:4:20] i = 3
// [src/main.rs:4:20] i = 4

练习 2

科拉茨序列(Collatz Sequence)的定义如下,对于任意大于零的 n1n_1

  • 如果 ni=1n_i = 1,那么序列在 nin_i 处终止。
  • 如果 nin_i 是偶数,则 ni+1=ni/2n_{i + 1} = n_i / 2
  • 如果 nin_i 是奇数,则 ni+1=3×ni+1n_{i + 1} = 3 \times n_i + 1

例如,从 n1=3n_1 = 3 开始:

  • 3 是奇数,所以 n2=3×3+1=10n_2 = 3 \times 3 + 1 = 10
  • 10 是偶数,所以 n3=10/2=5n_3 = 10 / 2 = 5
  • 5 是奇数,所以 n4=n_4 = 3 \times 5 + 1 = 16$;
  • 16 是偶数,所以 n5=16/2=8n_5 = 16 / 2 = 8
  • 8 是偶数,所以 n6=8/2=4n_6 = 8 / 2 = 4
  • 4 是偶数,所以 n7=4/2=2n_7 = 4 / 2 = 2
  • 2 是偶数,所以 n8=1n_8 = 1
  • 序列在此处终止。

编写一个函数,用于计算给定初始值 nn 的科拉茨序列的长度。

fn collatz_length(mut n: i32) -> u32 {
  todo!("Implement this")
}

fn main() {
    println!("Length: {}", collatz_length(11)); // should be 15
}

数组与元组

4.jpg Rust 中的数组:

fn main() {
    let mut a: [i8; 5] = [5, 4, 3, 2, 1];
    a[2] = 0;
    println!("a: {a:?}");
}

// Output
// a: [5, 4, 0, 2, 1]

注意这里的打印,使用了 {a:?}——一种格式化字符串的语法,用于打印调试信息。

Rust 还提供另一种类型,元组:

fn main() {
    let t: (i8, bool) = (7, true);
    dbg!(t.0);
    dbg!(t.1);
}

// Output
// [src/main.rs:3:5] t.0 = 7
// [src/main.rs:4:5] t.1 = true

获取元组中的元素,使用 .0.1 这种语法,类似元素在元组中的下标。

for 语句支持遍历数组(但不支持遍历元组):

fn main() {
    let primes = [2, 3, 5, 7, 11, 13, 17, 19];
    for prime in primes {
        for i in 2..prime {
            assert_ne!(prime % i, 0);
        }
    }
}

Rust 支持使用模式匹配将元组等较大的值解构为其组成部分:

fn check_order(tuple: (i32, i32, i32)) -> bool {
    let (left, middle, right) = tuple; // 一种解构方法
    left < middle && middle < right
}

fn main() {
    let tuple = (1, 5, 3);
    println!(
        "{tuple:?}: {}",
        if check_order(tuple) { "ordered" } else { "unordered" }
    );
}

// Output
// (1, 5, 3): unordered

练习 3

数组可以包含其他数组:

let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]];

小疑问:这个变量 array 的类型是什么?(编译器会给予提示)

使用上述这样的数组编写一个 transpose 函数,该函数将对矩阵进行转置(将行转换为列):

test.png

fn transpose(matrix: [[i32; 3]; 3]) -> [[i32; 3]; 3] {
    todo!()
}

fn main() {
    let matrix = [
        [101, 102, 103], // <-- the comment makes rustfmt add a newline
        [201, 202, 203],
        [301, 302, 303],
    ];

    dbg!(matrix);
    let transposed = transpose(matrix);
    dbg!(transposed);
}

引用

5.jpg

引用提供了一种在不获取值的所有权的情况下访问另一个值的方法,也被称为 “借用”。共享引用是只读的,被引用的数据不能更改。

fn main() {
    let a = 'A';
    let b = 'B';

    let mut r: &char = &a;
    dbg!(*r);

    r = &b;
    dbg!(*r);
}
// Output
// [src/main.rs:6:5] *r = 'A'
// [src/main.rs:9:5] *r = 'B'

对类型 T 的共享引用的类型是 &T 。引用值是使用 & 运算符创建的。* 运算符“解引用”一个引用,得到它的值 。

例如上面的例子中,通过 &a 创建一个引用 r,然后使用 *r 获取这个引用的值。

独占引用,也称为可变引用,允许更改它们所引用的值。它们的类型是 &mut T

fn main() {
    let mut point = (1, 2);
    let x_coord = &mut point.0;
    *x_coord = 20;
    println!("point: {point:?}");
}
// Output
// point: (20, 2)

切片让你能够查看更大的集合:

fn main() {
    let mut a: [i32; 6] = [10, 20, 30, 40, 50, 60];
    println!("a: {a:?}");

    let s: &mut [i32] = &mut a[2..4]; // 这里创建了一个可变的数组切片
    s[0] = 100i32;
    println!("s: {s:?}");
    println!("a: {a:?}");
}

// Output
// a: [10, 20, 30, 40, 50, 60]
// s: [100, 40]
// a: [10, 20, 100, 40, 50, 60]

切片从被切片的类型中借用数据。

现在我们可以理解 Rust 中的两种字符串类型了:

  • &str 是一个 UTF-8 编码字节的切片,类似于 &[u8]
  • String 是一个拥有所有权的 UTF-8 编码字节缓冲区,类似于 [u8]
fn main() {
    let s1: &str = "World";
    println!("s1: {s1}");

    let mut s2: String = String::from("Hello ");
    println!("s2: {s2}");

    s2.push_str(s1);
    println!("s2: {s2}");

    let s3: &str = &s2[2..9];
    println!("s3: {s3}");
}

// Output
// s1: World
// s2: Hello 
// s2: Hello World
// s3: llo Wor

Rust 对引用强制执行一些规则,以确保它们始终可以安全使用。

  • 引用永远不能为空,这样在使用时无需进行空值检查。
  • 引用的生命周期不能超过它们所指向的数据的生命周期。

现在,我们探讨第二条:

fn main() {
    let x_ref = {
        let x = 10;
        &x
    };
    dbg!(x_ref);
}

// 这段代码并不能通过编译,原因就是 `x` 的生命周期要短于 `x_ref`。

练习 4


// 计算向量各坐标的平方和并取平方根来计算向量的大小。使用 `sqrt()` 方法来计算平方根,例如 `v.sqrt()`。
fn magnitude(...) -> f64 {
    todo!()
}

// 计算向量的大小并将其所有坐标除以该大小来对向量进行归一化。
fn normalize(...) {
    todo!()
}

fn main() {
    println!("Magnitude of a unit vector: {}", magnitude(&[0.0, 1.0, 0.0]));

    let mut v = [1.0, 2.0, 9.0];
    println!("Magnitude of {v:?}: {}", magnitude(&v));
    normalize(&mut v);
    println!("Magnitude of {v:?} after normalization: {}", magnitude(&v));
}

自定义类型

6.jpg

CC++ 一样,Rust 支持自定义结构体:

struct Person {
    name: String,
    age: u8,
}

fn describe(person: &Person) {
    println!("{} is {} years old", person.name, person.age);
}

fn main() {
    let mut peter = Person {
        name: String::from("Peter"),
        age: 27,
    };
    describe(&peter);

    peter.age = 28;
    describe(&peter);

    let name = String::from("Avery");
    let age = 39;
    let avery = Person { name, age };
    describe(&avery);
}

// Output
// Peter is 27 years old
// Peter is 28 years old
// Avery is 39 years old

如果字段名称不重要,可以使用元组结构体:

struct Point(i32, i32);

fn main() {
    let p = Point(17, 23);
    println!("({}, {})", p.0, p.1);
}

这通常用于单字段包装器(称为新类型,Kotlin 中有个类似的写法,是 value class):

struct PoundsOfForce(f64);
struct Newtons(f64);

fn compute_thruster_force() -> PoundsOfForce {
    todo!("Ask a rocket scientist at NASA")
}

fn set_thruster_force(force: Newtons) {
    // ...
}

fn main() {
    let force = compute_thruster_force();
    set_thruster_force(force);
}

enum 关键字允许创建具有几种不同变体的类型(Rust 中的枚举是非常强大的):

#[derive(Debug)]
enum Direction {
    Left,
    Right,
}

#[derive(Debug)]
enum PlayerMove {
    Pass,                        // 最简单的
    Run(Direction),              // 元组
    Teleport { x: u32, y: u32 }, // 结构体
}

fn main() {
    let dir = Direction::Left;
    let player_move: PlayerMove = PlayerMove::Run(dir);
    println!("On this turn: {player_move:?}");
}

类型别名是为另一种类型创建的名称。这两种类型可以互换使用:

enum CarryableConcreteItem {
    Left,
    Right,
}

type Item = CarryableConcreteItem;

// 对于冗长、复杂的类型,别名更为有用
use std::cell::RefCell;
use std::sync::{Arc, RwLock};
type PlayerInventory = RwLock<Vec<Arc<RefCell<Item>>>>; // 这个类型就特别冗长

常量在编译时求值,其值会在使用它们的任何地方内联:

const DIGEST_SIZE: usize = 3;
const FILL_VALUE: u8 = calculate_fill_value();

const fn calculate_fill_value() -> u8 {
    if DIGEST_SIZE < 10 {
        42
    } else {
        13
    }
}

fn compute_digest(text: &str) -> [u8; DIGEST_SIZE] {
    let mut digest = [FILL_VALUE; DIGEST_SIZE];
    for (idx, &b) in text.as_bytes().iter().enumerate() {
        digest[idx % DIGEST_SIZE] = digest[idx % DIGEST_SIZE].wrapping_add(b);
    }
    digest
}

fn main() {
    let digest = compute_digest("Hello");
    println!("digest: {digest:?}");
}

// Output
// digest: [222, 254, 150]

const 的语义与 C++ 中的 constexpr 类似。

静态变量在程序的整个执行过程中都存在,因此不会被移动 :

static BANNER: &str = "Welcome to RustOS 3.14";

fn main() {
    println!("{BANNER}");
}

练习 5

我们将创建一个数据结构来表示电梯控制系统中的一个事件。由你来定义构建各种事件的类型和函数。使用 #[derive(Debug)] 以便能够使用 {:?} 对这些类型进行格式化输出。

本练习只需要创建并填充数据结构,以确保 main 函数能无错误运行。本教程的下一部分将介绍如何从这些结构中获取数据。

#![allow(dead_code)]

#[derive(Debug)]
/// 电梯系统中控制器必须做出响应的一个事件
enum Event {
    // TODO: 添加所需的字段
}

/// 行进方向
#[derive(Debug)]
enum Direction {
    Up,
    Down,
}

/// 轿厢已到达指定楼层
fn car_arrived(floor: i32) -> Event {
    todo!()
}

/// 轿厢门打开
fn car_door_opened() -> Event {
    todo!()
}

/// 轿厢门关闭
fn car_door_closed() -> Event {
    todo!()
}

/// 在指定楼层的电梯厅里按下了一个方向按钮
fn lobby_call_button_pressed(floor: i32, dir: Direction) -> Event {
    todo!()
}

/// 在轿厢内按下了一个楼层按钮.
fn car_floor_button_pressed(floor: i32) -> Event {
    todo!()
}

fn main() {
    println!(
        "A ground floor passenger has pressed the up button: {:?}",
        lobby_call_button_pressed(0, Direction::Up)
    );
    println!("The car has arrived on the ground floor: {:?}", car_arrived(0));
    println!("The car door opened: {:?}", car_door_opened());
    println!(
        "A passenger has pressed the 3rd floor button: {:?}",
        car_floor_button_pressed(3)
    );
    println!("The car door closed: {:?}", car_door_closed());
    println!("The car has arrived on the 3rd floor: {:?}", car_arrived(3));
}

练习答案

练习 1

fn fib(n: u32) -> u32 {
    if n < 2 {
        return n;
    } else {
        return fib(n - 1) + fib(n - 2);
    }
}

fn main() {
    let n = 20;
    println!("fib({n}) = {}", fib(n));
}

练习 2

fn collatz_length(mut n: i32) -> u32 {
    let mut len = 1;
    while n > 1 {
        n = if n % 2 == 0 { n / 2 } else { 3 * n + 1 };
        len += 1;
    }
    len
}

fn main() {
    println!("Length: {}", collatz_length(11)); // should be 15
}

练习 3

fn transpose(matrix: [[i32; 3]; 3]) -> [[i32; 3]; 3] {
    let mut result = [[0; 3]; 3];
    for i in 0..3 {
        for j in 0..3 {
            result[j][i] = matrix[i][j];
        }
    }
    result
}

fn main() {
    let matrix = [
        [101, 102, 103], // <-- the comment makes rustfmt add a newline
        [201, 202, 203],
        [301, 302, 303],
    ];

    dbg!(matrix);
    let transposed = transpose(matrix);
    dbg!(transposed);
}

练习 4


fn magnitude(vector: &[f64; 3]) -> f64 {
    let mut mag_squared = 0.0;
    for coord in vector {
        mag_squared += coord * coord;
    }
    mag_squared.sqrt()
}

fn normalize(vector: &mut [f64; 3]) {
    let mag = magnitude(vector);
    for item in vector {
        *item /= mag;
    }
}

fn main() {
    println!("Magnitude of a unit vector: {}", magnitude(&[0.0, 1.0, 0.0]));

    let mut v = [1.0, 2.0, 9.0];
    println!("Magnitude of {v:?}: {}", magnitude(&v));
    normalize(&mut v);
    println!("Magnitude of {v:?} after normalization: {}", magnitude(&v));
}

练习 5

#![allow(dead_code)]

#[derive(Debug)]
enum Event {
    /// 按钮按下.
    ButtonPressed(Button),

    /// 轿厢达到制定楼层.
    CarArrived(Floor),

    /// 轿厢门打开.
    CarDoorOpened,

    /// 轿厢门关闭.
    CarDoorClosed,
}

/// 楼层使用 Floor 来表示
type Floor = i32;

/// 电梯的运行方向
#[derive(Debug)]
enum Direction {
    Up,
    Down,
}

/// 用户可操作的按钮
#[derive(Debug)]
enum Button {
    /// 楼层电梯厅的一个按钮(电梯外)
    LobbyCall(Direction, Floor),

    /// 轿厢内的楼层按钮(电梯内)
    CarFloor(Floor),
}


fn car_arrived(floor: i32) -> Event {
    Event::CarArrived(floor)
}


fn car_door_opened() -> Event {
    Event::CarDoorOpened
}


fn car_door_closed() -> Event {
    Event::CarDoorClosed
}


fn lobby_call_button_pressed(floor: i32, dir: Direction) -> Event {
    Event::ButtonPressed(Button::LobbyCall(dir, floor))
}

fn car_floor_button_pressed(floor: i32) -> Event {
    Event::ButtonPressed(Button::CarFloor(floor))
}

fn main() {
    println!(
        "A ground floor passenger has pressed the up button: {:?}",
        lobby_call_button_pressed(0, Direction::Up)
    );
    println!("The car has arrived on the ground floor: {:?}", car_arrived(0));
    println!("The car door opened: {:?}", car_door_opened());
    println!(
        "A passenger has pressed the 3rd floor button: {:?}",
        car_floor_button_pressed(3)
    );
    println!("The car door closed: {:?}", car_door_closed());
    println!("The car has arrived on the 3rd floor: {:?}", car_arrived(3));
}