Rust基础总结

rust 基础总结

  • 基础的语法和常用表达式写法
  • 常见结构、类、算法的实现
  • 常见库的说明和使用
  • 与其他语言交互
  • 总览优缺点
  • 一个项目

basic

https://blog.csdn.net/bbdxf/article/details/78798809
很多基础的类型和操作都很简单,不做说明。下面仅列举一些最容易产生阻碍性疑问的点。

  • &引用
  • *解引用

一个及其重要,但是与实际“看似”矛盾的问题,借用(borrow):

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let a:i32 = 10;
    let b = a;
    println!("{:?} {:?}", a, b);

    let p1 = Point { x: 10, y: 20 };
    let p2 = p1;
    println!("{:?} {:?}", p1, p2);
}
/*
编译错误:
error[E0382]: borrow of moved value: `p1`
13 |     let p2 = p1;
   |              -- value moved here
14 |     println!("{:?} {:?}", p1, p2);
   |                           ^^ value borrowed here after move
*/

上面的代码,看似a/bp1/p2行为一模一样,为什么a/b可正常访问,但是p1/p2就不行?

在Rust中,有一个叫做借用(borrow)的东西,它会将变量的使用权转移,转以后,原始变量就会变得不可用。这对于很“纯洁”的数据类型/结构是适用的,比如我们创建的Point类型。

原则上而言,let p1 = Point{x:10, y:20};创建了一个p1对象,此时正常访问没问题,然后let p2 = p1;,它将p1的所有权转交(borrow of move value)给p1,此时p1就无法在此被正常访问了,只有p2可以。这个流程是符合Rust关于borrow的设计原则的。

那,问题来了?为什么a/b就可以,它们违背了Rust的设计原则了吗?

答案肯定是:没有违背!原因在于,Rust不仅有借用原则,还有Copy/Clone默认行为!这一点在大部分教程中都没有作为关键点提出,所以,不明白的童鞋看到上述代码就会很迷茫。由于Rust有部分类型默认实现了std::marker::Copytrait, 也就是整型浮点型这类基本类型。像 structs 这类没有默认实现的类型, 想要这样就得实现一下Copy/Clone.

#[derive(Debug)]
struct Point {
    x: f64,
    y: f64,
}

impl Clone for Point {
    fn clone(&self) -> Self {
        Self { x: self.x, y: self.y }
    }
}

impl Copy for Point {}

现在终于好使了。但是我们发觉做这些操作非常烦, 我们注意到 #[derive(Debug)] 这个东西, 刚好 Rust 提供了 Clone , Copy 的属性, 所以最终代码为:

#[derive(Debug, Copy, Clone)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let a = 10;
    let b = a;
    println!("{:?} {:?}", a, b);

    let p1 = Point { x: 10, y: 20 };
    let p2 = p1;
    println!("{:?} {:?}", p1, p2);
}
/*
10 10
Point { x: 10, y: 20 } Point { x: 10, y: 20 }
完美!
*/

基本类型解决了,我们处理下传参问题,以及指针问题:

一个函数传参的Demo:

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn func1(a: i32) -> i32 {
    println!("fun1 {:?}", a);
    a * 2
}

fn func2(a: &mut i32) {
    println!("fun2 {:?}", a);
    *a = 111;
}

fn func3(p: Point) -> Point {
    println!("fun3 {:?} {:?}", p.x, p.y);
    Point { x: 1, y: 2 }
}

fn func4(p: &mut Point) {
    println!("fun4 {:?} {:?}", p.x, p.y);
    p.x = 23;
    p.y *= 2;
}

// func5 
fn func5() -> &String{
    let s = String::from("21313");
    &s // error! s is out of it's lifetime
}

fn main() {
    let a = 10;
    println!("{:?} {:?}", func1(a), a);
    let mut b = a;
    func2(&mut b);
    println!("{:?} {:?}", a, b); // a has copy trait

    let p1 = Point { x: 100, y: 200 };
    let p = func3(p1);
    // println!("{:?} {:?}", p1.x, p1.y); // p1 has not copy trait, so error
    println!("{:?} {:?}", p.x, p.y);
    // func4(&mut p); error for p is not mut
    let mut p2 = p;
    func4(&mut p2);
    println!("{:?} {:?}", p2.x, p2.y);
}

指针问题,单独一个Demo:

fn main() {
    let x = 5;
    let raw = &x as *const i32;
    // println!("raw points at {}", *raw); *raw is unsafe
    println!("{:?}", raw); // raw is address of x, access is ok
    let pt_raw = unsafe { *raw }; // 明确指明unsafe
    println!("{:?}", pt_raw);  // ok

    let raw2 = &x;
    println!("{:?}", raw2); // access raw2 is value of x, raw2 is a ref of x (safe ref)
    let pt_raw2 = raw2 as *const i32;
    println!("{:?}", pt_raw2);  // ok, pt_raw2 == raw

    let a:i32 = 123;
    let b = a;
    let p1 = &a as *const i32;
    let p2 = &b as *const i32;
    println!("{:?} {:?}", p1, p2);
}
/*
0x7ce7d6f814
5
5
0x7ce7d6f814
0x7ce7d6f978 0x7ce7d6f97c
*/

一个重要的数据类型string:

fn main() {
    let s1: &str = "hello world, china!"; // 常量字符串,生命周期整个程序
    let s2: String = String::from(s1);
    let mut s3: String = s1.to_string();
    let s4: &str = s2.as_str(); // == &s2

    s3.push_str(" 1233 ");
    let s5 = s3.clone() + "#@!";  // 直接使用s3会被borrow,无法再次访问
    println!("{:?} {:?}  {:?}", s4, s3, s5);

    let s6 = "001.忠犬ハチ公";
    println!("len1: {:?}", s6.len()); // UTF-8字节长度
    println!("len2: {:?}", s6.chars().count()); // Unicode长度
    // 按照UTF-8编码,逐字节遍历
    for (i, bt) in s6.as_bytes().iter().enumerate(){
        print!("{:?}=>{:?} ", i, bt)
    }
    println!();
    // 按照Unicode编码,逐字符遍历
    for (i, ch) in s6.chars().enumerate(){
        print!("{:?}=>{:?} ", i, ch)
    }
}
/*
len1: 19
len2: 9
0=>48 1=>48 2=>49 3=>46 4=>229 5=>191 6=>160 7=>231 8=>138 9=>172 10=>227 11=>131 12=>143 13=>227 14=>131 15=>129 16=>229 17=>133 18=>172 
0=>'0' 1=>'0' 2=>'1' 3=>'.' 4=>'忠' 5=>'犬' 6=>'ハ' 7=>'チ' 8=>'公' 
*/

Slice, 特殊的是需要使用&才可使用:

fn main() {
    let mut a = [1, 2, 3, 4];
    a[2] = 10;
    println!("{:?}", a[2]);
    let b = &a[..];
    println!("{:?}", b);
    let c = &a[1..3]; // 不包含最后一个
    println!("{:?}", c);

    let s1 = "hello,world";
    println!("{:?}", &s1[..=5]); // ..= 包含最后一个字符
}
/*
10
[1, 2, 10, 4]
[2, 10]
"hello,"
*/

动态数组:

fn main() {
    let mut a= vec![25,21,3];
    println!("{:?}", a);
    for i in &mut a{
        *i = *i * 10;
    }
    println!("{:?}", a);
}
/*
[25, 21, 3]
[250, 210, 30]
*/

元组:

fn func() -> (i32, f32){
    (231, 2.311)
}
fn main() {
    let t1 = (23, "1312");
    println!("{:?}", t1);
    let t = func();
    let (a,_) = func();
    println!("{:?} {} ", t, a);
}
/*
(23, "1312")
(231, 2.311) 231
*/

Rust中的值都可以是一个表达式:

fn func(a: i32) -> i32 {
    if a < 10 {
        a * 10
    } else {
        a / 10
    }
}

fn main() {
    println!("{}", func(9));
    let a = {
        let y = 10;
        y + 1
    };
    let b = if a > 10 {
        true
    } else {
        false
    };
    println!("{} {}", a, b);
}

循环,比较特别的是loop可以有返回值:

fn main() {
    // if
    // ...
    // loop
    let mut i = 0;
    loop{
        println!("loop to dead! {}", i);
        i += 1;
        if i>5{
            break;
        }
    }
    // loop can return value
    i = 1;
    let a = loop{
        i *= 2;
        if i>1000 {
            break i;
        }
    };
    println!("{}", a);

    // for
    let b = [23, 11, 123, 11];
    for (i,v) in b.iter().enumerate(){
        println!("{} {}",i, v);
    }
}

限制:

  • 同一作用域,不能存在两个可变引用
  • 同一作用域,不能同时存在可变和不可变引用

即,在同一作用域中,要么同时存在多个不可变引用,要么仅存在一个可变引用,其他的情况都违规。

fn main(){
    let mut a = String::from("23123");
    let b = &mut a; // ok
    let c = &mut a; // error, 同一作用域中,不能同时有两个可变引用
}

fn main() {
    let mut a = String::from("asdasd");
    let b = &a; // ok
    let c = &mut a; // error, 同一作用域中,不能同时存在可变/不可变引用
    println!("{} {}", c, b);
}

Life time

Lifetime annotations have a slightly unusual syntax: the names of lifetime parameters must start with an apostrophe (') and are usually all lowercase and very short, like generic types. Most people use the name 'a. We place lifetime parameter annotations after the & of a reference, using a space to separate the annotation from the reference’s type.

&i32        // a reference
&'a i32     // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

One special lifetime we need to discuss is 'static, which denotes the entire duration of the program. All string literals have the 'static lifetime, which we can annotate as follows:

let s: &'static str = "I have a static lifetime.";

一个关系到生命周期的隐形例子

struct Point {
    x: i32,
    y: i32,
}

struct PointRef<'a> {
    x: &'a mut i32,
    y: &'a mut i32,
}

fn main() {
    let mut p1 = Point { x: 10, y: 12 };
    p1.x = 100;
    {
        println!("Point: {}, {}", p1.x, p1.y); // 1, 这里可以用. immutable borrow
        let p2 = PointRef { x: &mut p1.x, y: &mut p1.y };  // mutable borrow
        *p2.y = 111;
        // println!("Point: {}, {}", p1.x, p1.y); // 2, 这里不行. immutable borrow
        println!("PointRef: {} {}", p2.x, p2.y);
    }
}

basic_struct

结构体和接口

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {  // 方法
        self.width * self.height
    }
}

impl Rectangle {  // 这里,允许使用多个impl块来定义 Rectangle
    fn square(size: u32) -> Rectangle {  // 关联函数,没有self参数,类似静态方法调用
        Rectangle { width: size, height: size }
    }
}

fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    println!("The area of the rectangle is {} square pixels.",rect1.area());
}

枚举:

enum EnumA {
    IPV4,
    IPV6,
    None
}

enum EnumB {
    Man(String),
    Woman(String),
    None
}

fn main() {
    let a = EnumA::IPV4;
    let b = EnumB::Man(String::from("123"));
    let c = EnumB::Woman(String::from("abc"));
    // if b==c ..., error, can't compare with ==
    println!("{:?}", match a {
        EnumA::IPV4 => 1,
        EnumA::IPV6 => 2,
        _ => 0
    });

    println!("{:?}", match b {
        EnumB::Man(v) => v,
        EnumB::Woman(v) => v,
        _ => "None".to_string(),
    });

    // 简单比较
    if let EnumA::IPV4 = a {
        println!("a is IPV4");
    }
    if let EnumB::Woman( v ) = c {
        println!("c is woman {:?}", v);
    }
}

针对枚举书写代码过多,使用Option进行优化:

fn plus_one(x: Option) -> Option {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}
let five = Some(5);

MAP类型:

fn main() {
    use std::collections::HashMap;
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);
    let team_name = String::from("Blue");
    let score = scores.get(&team_name);
    println!("{} {}", team_name, score.unwrap());
    for (key, value) in &scores {
        println!("{}: {}", key, value);
    }
    let count = scores.entry("Balck".to_string()).or_insert(0); // 存在了获取,不存在就设置为0
    *count += 1;
}

错误处理

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    // 1
    let f = File::open("hello.txt");
    let f = match f {
        Ok(file) => file,
        Err(error) => {
            panic!("There was a problem opening the file: {:?}", error)
        },
    };
    // 2
    let f = File::open("hello.txt").map_err(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap_or_else(|error| {
                panic!("Tried to create file but there was a problem: {:?}", error);
            })
        } else {
            panic!("There was a problem opening the file: {:?}", error);
        }
    });
    // 3
    let f = File::open("hello.txt").unwrap();
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

// 使用 ? 传播错误, Ok()包裹正确的值
fn read_username_from_file() -> Result {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

闭包/匿名函数

let expensive_closure = |num: u32| -> u32 {
    println!("calculating slowly...");
    thread::sleep(Duration::from_secs(2));
    num
};

// normal
let mut num = 5;
{
    let mut add_num = |x: i32| num += x; 
    add_num(5);
}
assert_eq!(10, num);   

// move 
let mut num = 5;
{
    let mut add_num = move |x: i32| num += x;
    add_num(5);
}
assert_eq!(5, num);   

智能指针:

  • Box 在堆上分配
  • Rc 有引用计数的包裹类型,可以被多个所有者使用
  • RefRefMut,通过 RefCell 访问,一个在运行时而不是在编译时执行借用规则的类型
  • Arc原子引用计数,并发安全

List的实现:

enum List {
    Cons(i32, List),
    Nil,
}
use List::{Cons, Nil};

fn main() {
    let list = Cons(1, Cons(2, Cons(3, Nil)));
}

并发

  1. 线程
use std::thread;
use std::time::Duration;

fn main() {
    thread::spawn(|| {
        for i in 1..10 {
            println!("hi number {} from the spawned thread!", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("hi number {} from the main thread!", i);
        thread::sleep(Duration::from_millis(1));
    }
}
  1. 管道通信
use std::thread;
use std::sync::mpsc;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let val = String::from("hi");
        tx.send(val).unwrap();
    });

    let received = rx.recv().unwrap();
    println!("Got: {}", received);
}

TCPServer Demo

use std::io::prelude::*;
use std::net::TcpStream;
use std::net::TcpListener;
use std::thread;

fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();

    for stream in listener.incoming() {
        let stream = stream.unwrap();

        thread::spawn(|| {
            handle_connection(stream);
        });
    }
}

fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 512];
    stream.read(&mut buffer).unwrap();
    println!("Request: {}", String::from_utf8_lossy(&buffer[..]));

    let response = "HTTP/1.1 200 OK\r\n\r\n";
    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();
}

链表(简单版)

use List::*;

enum List {
    // Cons: 元组结构体,包含一个元素和一个指向下一节点的指针
    Cons(u32, Box),
    // Nil: 末结点,表明链表结束
    Nil,
}

// 方法可以在 enum 定义
impl List {
    // 创建一个空列表
    fn new() -> List {
        // `Nil` 为 `List` 类型
        Nil
    }

    // 处理一个列表,得到一个头部带上一个新元素的同样类型的列表并返回此值
    fn prepend(self, elem: u32) -> List {
        // `Cons` 同样为 List 类型
        Cons(elem, Box::new(self))
    }

    // 返回列表的长度
    fn len(&self) -> u32 {
        // `self` 必须匹配,因为这个方法的行为取决于 `self` 的变化类型
        // `self` 为 `&List` 类型,`*self` 为 `List` 类型,一个具体的 `T` 类型的匹配
        // 要参考引用 `&T` 的匹配
        match *self {
            // 不能得到 tail 的所有权,因为 `self` 是借用的;
            // 而是得到一个 tail 引用
            Cons(_, ref tail) => 1 + tail.len(),
            // 基本情形:空列表的长度为 0
            Nil => 0
        }
    }

    // 将列表以字符串(堆分配的)的形式返回
    fn stringify(&self) -> String {
        match *self {
            Cons(head, ref tail) => {
                // `format!` 和 `print!` 类似,但返回的是一个堆分配的字符串,而不是
                // 打印结果到控制台上
                format!("{}, {}", head, tail.stringify())
            },
            Nil => {
                format!("Nil")
            },
        }
    }
}

fn main() {
    // 创建一个空链表
    let mut list = List::new();

    // 追加一些元素
    list = list.prepend(1);
    list = list.prepend(2);
    list = list.prepend(3);

    // 显示链表的最后状态
    println!("linked list has length: {}", list.len());
    println!("{}", list.stringify());
}

闭包

闭包只有这三种模式,即

  • Fn:它只能从闭包环境中不可变借用相关变量;
  • FnMut:它能可变借用捕获的变量,并改变闭包环境;
  • FnOnce:它捕获的闭包环境变量,闭包本身只能用一次,不是说闭包中的变量被一次性消费,而是指闭包本身;

推荐资源

https://rustwiki.org/zh-CN//rust-by-example

LISP链式编程

fn is_odd(n: u32) -> bool {
    n % 2 == 1
}

fn main() {
    println!("Find the sum of all the squared odd numbers under 1000");
    let upper = 1000;

    // 命令式方式(imperative approach)
    // 声明累加器变量
    let mut acc = 0;
    // 重复:0,1, 2, ... 到无穷大
    for n in 0.. {
        // 数字的平方
        let n_squared = n * n;

        if n_squared >= upper {
            // 若大于上限(upper limit)则退出循环
            break;
        } else if is_odd(n_squared) {
            // 如果是奇数就累加值
            acc += n_squared;
        }
    }
    println!("imperative style: {}", acc);

    // 函数式方式(functional approach)
    let sum_of_squared_odd_numbers: u32 =
        (0..).map(|n| n * n)             // 所有自然数的平方
            .take_while(|&n| n < upper) // 小于上限
            .filter(|&n| is_odd(n))     // 为奇数
            .fold(0, |sum, i| sum + i); // 最后其后
    println!("functional style: {}", sum_of_squared_odd_numbers);
}

模块与可见性

模块默认是私有的,并且只要中间有一个私有的,其后路径上pub的也不能访问。

// 一个名为 `my` 的模块
mod my {
    // 在模块中的项默认带有私有可见性。
    fn private_function() {
        println!("called `my::private_function()`");
    }

    // 使用 `pub` 修饰语来改变默认可见性。
    pub fn function() {
        println!("called `my::function()`");
    }

    // 在同一模块中,项可以访问其它项,即使是私有属性。
    pub fn indirect_access() {
        print!("called `my::indirect_access()`, that\n> ");
        private_function();
    }

    // 项也可以嵌套。
    pub mod nested {
        pub fn function() {
            println!("called `my::nested::function()`");
        }

        #[allow(dead_code)]
        fn private_function() {
            println!("called `my::nested::private_function()`");
        }
    }

    // 嵌套项的可见性遵循相同的规则。
    mod private_nested {
        #[allow(dead_code)]
        pub fn function() {
            println!("called `my::private_nested::function()`");
        }
    }
}

fn function() {
    println!("called `function()`");
}

fn main() {
    // 模块允许在拥有相同名字的项之间消除歧义。
    function();
    my::function();

    // 公有项,包括内部嵌套的公有项,可以在父级的模块中访问到。
    my::indirect_access();
    my::nested::function();

    // 一个模块中的私有项不能被直接访问,即使私有项嵌套在公有的模块中:

    // 报错!`private_function` 是私有的。
    //my::private_function();
    // 试一试 ^ 将此行注释去掉

    // 报错! `private_function` 是私有的。
    //my::nested::private_function();
    // 试一试 ^ 将此行注释去掉    

    // 报错! `private_nested` 是私有的模块。
    //my::private_nested::function();
    // 试一试 ^ 将此行注释去掉    

}

结构体成员也默认是私有的。

mod my {
    // 一个公有的结构体,带有一个公有的泛型类型 `T` 的字段
    pub struct WhiteBox {
        pub contents: T,
    }

    // 一个公开的结构体,带有一个私有的泛型类型 `T` 的字段
    #[allow(dead_code)]
    pub struct BlackBox {
        contents: T,
    }

    impl BlackBox {
        // 一个公有的构造器方法
        pub fn new(contents: T) -> BlackBox {
            BlackBox {
                contents: contents,
            }
        }
    }
}

fn main() {
    // 带有公有字段的公有的结构体,可以像平常一样构造
    let white_box = my::WhiteBox { contents: "public information" };

    // 并且它们的字段可以正常访问到。
    println!("The white box contains: {}", white_box.contents);

    // 带有私有字段的公有结构体不能使用字段名来构造。
    // 报错!`BlackBox` 含有私有字段。
    //let black_box = my::BlackBox { contents: "classified information" };
    // 试一试 ^ 将此行注释去掉


    // 不过带有私有字段的结构体可以使用公有的构造器来创建。
    let _black_box = my::BlackBox::new("classified information");

    // 并且一个结构体中的私有字段不能访问到。
    // 报错!`content` 字段是私有的。
    //println!("The black box contains: {}", _black_box.contents);
    // 试一试 ^ 将此行注释去掉    

}

按照文件划分模块:

|-- my
|   |-- inaccessible.rs  // mod inaccessible;
|   |-- mod.rs    // 模块总入口,管理这个文件夹下pub内容
|   `-- nested.rs // mod nested;
`-- main.rs // 主调用

crate

rustc 默认编译的是库,想要可执行文件需要添加参数改变它的行为。rustc --crate-type=lib rary.rs 编译为libxxx.rlib。其他程序可以从外部导入库:extern crate rary; 然后编译时添加参数rustc executable.rs --extern rary=libxxxx.rlib就可以啦。

如果是在项目中使用其他人编写的库,也是同样extern crate rary;。此时,也需要在Cargo.toml中添加dependencies中库对应的版本信息。

属性

属性可以接受参数,有不同的语法形式:

  • #[attribute = "value"]
  • #[attribute(key = "value")]
  • #[attribute(value1, value2, ...)]

一些固定的属性:

// 这个 crate 是一个库文件
#![crate_type = "lib"]
// 库的名称为 “rary”
#![crate_name = "rary"]

//////////////////////////////////

// 这个函数仅当操作系统是 Linux 的时候才会编译
#[cfg(target_os = "linux")]
fn are_you_on_linux() {
    println!("You are running linux!")
}

// 而这个函数仅当操作系统**不是** Linux 时才会编译
#[cfg(not(target_os = "linux"))]
fn are_you_on_linux() {
    println!("You are *not* running linux!")
}

fn main() {
    are_you_on_linux();

    println!("Are you sure?");
    if cfg!(target_os = "linux") {
        println!("Yes. It's definitely linux!");
    } else {
        println!("Yes. It's definitely *not* linux!");
    }
}

自定义属性/配置:

#[cfg(some_condition)]
fn conditional_function() {
    println!("condition met!")
}

// rustc --cfg some_condition custom.rs && ./custom
// 否则,会提示编译找不到 conditional_function

impl和Trait

trait是定义特性,impl是实现定义的特性。
如下两种方式实现:

trait Pilot {
    fn fly1(&self);
}

trait Wizard {
    fn fly2(&self);
}

struct Human;

impl Pilot for Human {
    fn fly1(&self) {
        println!("This is your captain speaking.");
    }
}

impl Wizard for Human {
    fn fly2(&self) {
        println!("Up!");
    }
}

impl Human {
    fn fly(&self) {
        println!("*waving arms furiously*");
    }
}

fn main() {
    let a = Human {};
    a.fly();
    a.fly1();
    a.fly2();
}

你可能感兴趣的:(Rust)