0%

canbus

底层can

CanClient是底层can的抽象类,表示某一种can设备的接口

1
2
3
4
5
6
7
8
class CanClient {
virtual bool Init(const CANCardParameter &parameter) = 0;
virtual apollo::common::ErrorCode Start() = 0;
virtual apollo::common::ErrorCode Send(const std::vector<CanFrame> &frames,
int32_t *const frame_num) = 0;
virtual apollo::common::ErrorCode Receive(std::vector<CanFrame> *const frames,
int32_t *const frame_num) = 0;
}

can client由工厂模式创建,不同参数指定不同 can client

1
2
3
auto can_factory = CanClientFactory::Instance();
can_factory->RegisterCanClients();
can_client_ = can_factory->CreateCANClient(canbus_conf_.can_card_parameter());

以socket为例

1
class SocketCanClientRaw : public CanClient

继承了CanClient,实现了其中的虚函数,主要就是调用linux原生的socket api

底盘数据抽象

不同车辆底盘数据、协议不同,使用了vehicle factory来创建不同车辆的 factory

1
2
3
4
VehicleFactory vehicle_factory;
vehicle_factory.RegisterVehicleFactory();
auto vehicle_object =
vehicle_factory.CreateVehicle(canbus_conf_.vehicle_parameter());

每一个车型的factory拥有自己的 controller 和 message manager

1
2
message_manager_ = vehicle_object->CreateMessageManager();
vehicle_controller_ = vehicle_object->CreateVehicleController();

其中controller似乎用于填pb等等事情的

message_manager则用于管理各个信号的decode与encode

以”ch vehicle”为例子,

工厂类:

1
class ChVehicleFactory : public AbstractVehicleFactory

controller类:

1
class ChController final : public VehicleController

message manager类:

1
class ChMessageManager : public MessageManager<::apollo::canbus::ChassisDetail>

先看下message manager的构造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ChMessageManager::ChMessageManager() {
// Control Messages
AddSendProtocolData<Brakecommand111, true>();
AddSendProtocolData<Controlcommand115, true>();
AddSendProtocolData<Gearcommand114, true>();
AddSendProtocolData<Steercommand112, true>();
AddSendProtocolData<Throttlecommand110, true>();
AddSendProtocolData<Turnsignalcommand113, true>();

// Report Messages
AddRecvProtocolData<Brakestatus511, true>();
AddRecvProtocolData<Ecustatus1515, true>();
AddRecvProtocolData<Ecustatus2516, true>();
AddRecvProtocolData<Ecustatus3517, true>();
AddRecvProtocolData<Gearstatus514, true>();
AddRecvProtocolData<Steerstatus512, true>();
AddRecvProtocolData<Throttlestatus510, true>();
AddRecvProtocolData<Turnsignalstatus513, true>();
}

可以看到message manager中添加了该vehicle的各个收发信号

Brakestatus511信号为例

1
2
class Brakestatus511 : public ::apollo::drivers::canbus::ProtocolData<
::apollo::canbus::ChassisDetail>

实现了 parse函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void Brakestatus511::Parse(const std::uint8_t* bytes, int32_t length,
ChassisDetail* chassis) const {
chassis->mutable_ch()->mutable_brake_status__511()->set_brake_pedal_en_sts(
brake_pedal_en_sts(bytes, length));
chassis->mutable_ch()->mutable_brake_status__511()->set_brake_pedal_sts(
brake_pedal_sts(bytes, length));
chassis->mutable_ch()->mutable_brake_status__511()->set_brake_err(
brake_err(bytes, length));
chassis->mutable_ch()->mutable_brake_status__511()->set_emergency_btn_env(
emergency_btn_env(bytes, length));
chassis->mutable_ch()->mutable_brake_status__511()->set_front_bump_env(
front_bump_env(bytes, length));
chassis->mutable_ch()->mutable_brake_status__511()->set_back_bump_env(
back_bump_env(bytes, length));
chassis->mutable_ch()->mutable_brake_status__511()->set_overspd_env(
overspd_env(bytes, length));
chassis->mutable_check_response()->set_is_esp_online(
brake_pedal_en_sts(bytes, length) == 1);
}

某个信号的解析如下,这里就是从can frame中按照dbc定义的信号格式解析了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// config detail: {'description': 'brake pedal enable bit(Status)', 'enum': {0:
// 'BRAKE_PEDAL_EN_STS_DISABLE', 1: 'BRAKE_PEDAL_EN_STS_ENABLE', 2:
// 'BRAKE_PEDAL_EN_STS_TAKEOVER'}, 'precision': 1.0, 'len': 8, 'name':
// 'brake_pedal_en_sts', 'is_signed_var': False, 'offset': 0.0,
// 'physical_range': '[0|1]', 'bit': 0, 'type': 'enum', 'order': 'intel',
// 'physical_unit': ''}
Brake_status__511::Brake_pedal_en_stsType Brakestatus511::brake_pedal_en_sts(
const std::uint8_t* bytes, int32_t length) const {
Byte t0(bytes + 0);
int32_t x = t0.get_byte(0, 8);
Brake_status__511::Brake_pedal_en_stsType ret =
static_cast<Brake_status__511::Brake_pedal_en_stsType>(x);
return ret;
}

再来看看controller类的实现,有一个重要函数

1
2
3
4
5
/**
* @brief calculate and return the chassis.
* @returns a copy of chassis. Use copy here to avoid multi-thread issues.
*/
Chassis chassis() override;

主要做的事情就是从message_manager中获得sensor data,然后填到自己的Chassis chassis_;变量中并返回,后面可以看到sensor data其实是前面parse函数填的内容

1
2
ChassisDetail chassis_detail;
message_manager_->GetSensorData(&chassis_detail);

总结一下,单以接收can信号为例,几个关键点

一种车型对应一个factory,该facotry会产生controller和message manager对象

其中 message manager封装了车身信号的decode和encode函数

controller则负责从message manager中获取解析后的数据,并存到Chassis chassis_(这是一个pb 结构体),然后提供API给外部使用

真正的报文接收处理流程

can报文的接收处理由CanReceiver类管理

CanReceiver由can_client和message manager初始化

并维护一个接收线程

1
2
template <typename SensorType>
void CanReceiver<SensorType>::RecvThreadFunc()

在这个线程loop中

使用can_client类的接口接收can frame,这里实际就会调用到socket api

1
can_client_->Receive(&buf, &frame_num)

使用message manager 解析刚刚收到的can frame,这里实际就会调用到对应message manager中的信号解析函数

1
pt_manager_->Parse(uid, data, len); //以Brakestatus511信号为例就是上面贴出来的parse函数了

在这步parse之后,数据已经填到sensor data了

数据的发布

CanBusComponent类:

1
2
3
4
5
6
void CanbusComponent::PublishChassis() {
Chassis chassis = vehicle_controller_->chassis();
common::util::FillHeader(node_->Name(), &chassis);
chassis_writer_->Write(chassis);
ADEBUG << chassis.ShortDebugString();
}

从controller中调用chassis接口,得到chassis数据,然后就使用protobuf writer发布出去了

问题:

  1. 当std::thread调用析构时,若thread没有正确退出(没有return),则会产生coredump,为什么?
  2. 若在user space捕捉SIGABRT信号,thread异常退出为何还会产生coredump?

先来看看coredump的产生

kernel space

内核在处理进程信号时,调用do_signal(arch/arm/kernel/signal.c)

1
static int do_signal(struct pt_regs *regs, int syscall)

接着调用get_signal处理信号,

1
2
3
if (get_signal(&ksig)) {
handle_signal(&ksig, regs);
}

取出信号,若用户有绑定signal handler,则返回non-zero值,调用用户handler,不进行内核默认处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool get_signal(struct ksignal *ksig) 
...
signr = dequeue_signal(current, &current->blocked, &ksig->info);
...
if (ka->sa.sa_handler == SIG_IGN) /* Do nothing. */
continue;
if (ka->sa.sa_handler != SIG_DFL) {
/* Run the handler. */
ksig->ka = *ka;

if (ka->sa.sa_flags & SA_ONESHOT)
ka->sa.sa_handler = SIG_DFL;

break; /* will return non-zero "signr" value */
}

若用户没有绑定signal handler,则使用内核默认处理方式

若signal在coredump信号列表定义内,则会产生coredump

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (sig_kernel_coredump(signr)) {
if (print_fatal_signals)
print_fatal_signal(ksig->info.si_signo);
proc_coredump_connector(current);
/*
* If it was able to dump core, this kills all
* other threads in the group and synchronizes with
* their demise. If we lost the race with another
* thread getting here, it set group_exit_code
* first and our do_group_exit call below will use
* that value and ignore the one we pass it.
*/
do_coredump(&ksig->info);
}

注意SIGABRT在coredump信号定义内

1
2
3
4
5
6
7
#define SIG_KERNEL_COREDUMP_MASK (\
rt_sigmask(SIGQUIT) | rt_sigmask(SIGILL) | \
rt_sigmask(SIGTRAP) | rt_sigmask(SIGABRT) | \
rt_sigmask(SIGFPE) | rt_sigmask(SIGSEGV) | \
rt_sigmask(SIGBUS) | rt_sigmask(SIGSYS) | \
rt_sigmask(SIGXCPU) | rt_sigmask(SIGXFSZ) | \
SIGEMT_MASK )

显然,线程退出时,进程收到了满足coredump的信号,才会产生coredump

接着看user space的实现,线程析构时,发出了什么信号

user space

局部对象实例在离开作用域时会被调用析构函数,std线程的析构函数如下:

若线程因一些原因如busy waiting而没有退出,其joinable会为true,则析构会调用terminate

terminate默认会调用abort

看下glibc中abort的实现:

https://code.woboq.org/userspace/glibc/stdlib/abort.c.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* Send signal which possibly calls a user handler.  */
if (stage == 1)
{
/* This stage is special: we must allow repeated calls of
`abort' when a user defined handler for SIGABRT is installed.
This is risky since the `raise' implementation might also
fail but I don't see another possibility. */
int save_stage = stage;
stage = 0;
__libc_lock_unlock_recursive (lock);
raise (SIGABRT);
__libc_lock_lock_recursive (lock);
stage = save_stage + 1;
}
/* There was a handler installed. Now remove it. */
if (stage == 2)
{
++stage;
memset (&act, '\0', sizeof (struct sigaction));
act.sa_handler = SIG_DFL;
__sigfillset (&act.sa_mask);
act.sa_flags = 0;
__sigaction (SIGABRT, &act, NULL);
}
/* Try again. */
if (stage == 3)
{
++stage;
raise (SIGABRT);
}

可以看到,abort会先发送一次SIGABRT

然后清掉用户捕捉信号的handler

再发送一次SIGABRT

conclusion

线程析构发生terminate时,会调用abort先产生一次SIGABRT

内核发现用户若有注册handler,则调用用户handler,此时不产生coredump

之后,abort函数清除掉用户的handler,再发一次ABRT信号

此时内核发现没有用户handler,走默认处理函数,ARBT信号产生coredump

所以线程异常退出产生coredump的原因是发出了SIGABRT信号

而即使在user space设置了SIGABRT的捕捉信号,由于abort函数的机制,仍然会产生coredump

Rust

Rust是由Mozilla主导开发的通用、编译型编程语言。设计准则为“安全、并发、实用”,支持函数式、并发式、过程式以及面向对象的编程风格。

特性:

  • 强类型静态语言

​ 编译时就知道变量类型的是静态,运行时才知道变量类型的是动态(解释性语言如python)

​ 不允许隐式转换的是强类型,允许隐式转换的是弱类型

img

  • 函数式编程

    函数式编程中的函数不是指计算机中的函数,而是数学中的函数,即自变量的映射f(x)。一个函数的值仅决定于函数参数的值,不依赖其他状态。比如sqrt(x)计算x的平方根,只要x不变,不论什么时候调用,调用几次,值都不变,所以纯函数式编程中,变量是代数中的变量,即一个值的名称,变量的值是不可变的,比如x = x + 1在数学代数里,这个等式为假,由于变量值是不可变的,对于值的操作并不是修改原来的值,而是修改新产生的值,原来的值保持不变

  • 没有GC,不用care alloc和free,以ownership和lifetime替代

    GC: garbage collection,显式申请内存,但不需要主动释放,GC在程序跑的时候会时不时的去找没有在用的memory并释放它 (Java)

    alloc/free: alloc和free要配对,显式申请的内存要主动的释放 (C)

    rust不需要以上两种,利用ownership和lifetime的特性,编译时就会检查很多要求,能编译过就没有内存问题(不存在空指针,内存泄露)

  • 零开销抽象zero cost abstraction 如C++

    提供面向对象,高级抽象,多态 (trait,impl,generic…. 类比C++ class,template,interface)

  • 强大的测试系统

    [test] ,单元测试的极致,在开发过程中可以测试任何一段code

安装

官网安装方法:

使用rustup,The Rust toolchain installer

获取rustup并运行:curl –proto ‘=https’ –tlsv1.2 -sSf https://sh.rustup.rs | sh

install path

rustc –version 检查安装成功

编译器为rustc,使用rustup可以配置toolchain等

编译单个文件为 rustc xxx.rs

多个文件使用cargo包管理工具

cargo用以上方法伴随着会安装

创建项目 cargo new xxx

里面会自动生成 配置文件和 主函数main.rs

Cargo.toml 是配置文件,类似于Makefile

[package]
name = “dbc”
version = “0.1.0”
authors = [“xxx”]
edition = “2018”

[dependencies]
can-dbc = { path = “./can-dbc” }
clap = “2.33”
derive_more = “0.99”
socketcan = “1.7”

[dependencies.nom]
version = “4.2”
features = [“verbose-errors”]

其中[dependecies] 定义了 依赖的库

执行cargo build即可编译出可执行文件或lib

vscode安装rust插件:ctrl+shitft+x

  • 输入 Rust,安装
  • 输入 rust-analyzer,安装

Basic

main.rs

1
2
3
fn main() {
println!("Hello, world!");
}

followed by rust-by-example: https://doc.rust-lang.org/rust-by-example/index.html

类型

基本:

  • signed integers: i8, i16, i32, i64, i128 and isize (pointer size)
  • unsigned integers: u8, u16, u32, u64, u128 and usize (pointer size)
  • floating point: f32, f64
  • char Unicode scalar values like 'a', 'α' and '∞' (4 bytes each)
  • bool either true or false
  • and the unit type (), whose only possible value is an empty tuple: ()

复合:

  • arrays like [1, 2, 3]
  • tuples like (1, true)

自定义:

  • struct: define a structure

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    struct Person {
    name: String,
    age: u8,
    }

    // A unit struct
    struct Unit; //useful for generics(template)

    // A tuple struct
    struct Pair(i32, f32);

    // A struct with two fields
    struct Point {
    x: f32,
    y: f32,
    }
  • enum: define an enumeration

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    enum WebEvent {
    // An `enum` may either be `unit-like`,
    PageLoad,
    PageUnload,
    // like tuple structs,
    KeyPress(char),
    Paste(String),
    // or c-like structures.
    Click { x: i64, y: i64 },
    }

赋值

using the let binding:

1
2
3
4
5
6
7
8
9
10
let a: i32 = 1;
let b: bool = true;
let pair = (1, true);
let ar: [i32; 5] = [1, 2, 3, 4, 5];

struct Point {
x: f32,
y: f32,
}
let point: Point = Point { x: 10.3, y: 0.4 };

默认的赋值操作都是immutable(不可修改)的,即赋值后,值不能再改了

使用mut关键字可将 不可修改的 变成 可修改的

1
2
3
4
5
6
7
8
9
10
11
12
let _immutable_binding = 1;
let mut mutable_binding = 1;

println!("Before mutation: {}", mutable_binding);

// Ok
mutable_binding += 1;

println!("After mutation: {}", mutable_binding);

// Error!
_immutable_binding += 1;

表达式

rust程序由表达式组成,以;结尾

1
2
3
4
5
6
7
8
9
fn main() {
// variable binding
let x = 5;

// expression;
x;
x + 1;
15;
}

中括号括起来的块(block)也是表达式:

1
2
3
4
5
6
7
let y = {
let x_squared = x * x;
let x_cube = x_squared * x;

// This expression will be assigned to `y`
x_cube + x_squared + x
};

block里的最后一句就是输出的表达式,但如果加了分号;,那么输出的结果就是()

1
2
3
4
let z = {
// The semicolon suppresses this expression and `()` is assigned to `z`
2 * x;
};

控制

  • if/else

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
       let n = 5;

    //if 后面接 bool condition
    if n < 0 {
    print!("{} is negative", n);
    } else if n > 0 {
    print!("{} is positive", n);
    } else {
    print!("{} is zero", n);
    }
  • loop/while

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    loop {
    //do something
    continue;
    //
    break;
    }

    //loop with conditon
    while n < 101 {
    //do something
    }
  • for and range

    使用for in接iterator

    1
    2
    3
    4
    5
       let names = vec!["Bob", "Frank", "Ferris"];

    for name in names.iter() {
    println!("Hello {}", name);
    }
  • match

    类似于C语言的switch

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let number = 13;

    match number {
    // Match a single value
    1 => println!("One!"),
    // Match several values
    2 | 3 | 5 | 7 | 11 => println!("This is a prime"),
    // Match an inclusive range
    13..=19 => println!("A teen"),
    // Handle the rest of cases
    _ => println!("Ain't special"),
    }

    match可以做destructing,很有用哦~

    • Destructuring Tuples

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      let triple = (0, -2, 3);

      // Match can be used to destructure a tuple
      match triple {
      // Destructure the second and third elements
      (0, y, z) => println!("First is `0`, `y` is {:?}, and `z` is {:?}", y, z),
      (1, ..) => println!("First is `1` and the rest doesn't matter"),
      // `..` can be the used ignore the rest of the tuple
      _ => println!("It doesn't matter what they are"),
      // `_` means don't bind the value to a variable
      }
    • Destructuring Enums

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      enum Color {
      // These 3 are specified solely by their name.
      Red,
      Blue,
      Green,
      // These likewise tie `u32` tuples to different names: color models.
      RGB(u32, u32, u32),
      HSV(u32, u32, u32),
      }

      fn main() {
      let color = Color::RGB(122, 17, 40);

      // An `enum` can be destructured using a `match`.
      match color {
      Color::Red => println!("The color is Red!"),
      Color::Blue => println!("The color is Blue!"),
      Color::Green => println!("The color is Green!"),
      Color::RGB(r, g, b) =>
      println!("Red: {}, green: {}, and blue: {}!", r, g, b),
      Color::HSV(h, s, v) =>
      println!("Hue: {}, saturation: {}, value: {}!", h, s, v),
      Color::HSL(h, s, l) =>
      println!("Hue: {}, saturation: {}, lightness: {}!", h, s, l),
      // Don't need another arm because all variants have been examined
      }
      }
    • Destructuring Structures

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      struct Foo {
      x: (u32, u32),
      y: u32,
      }

      let foo = Foo { x: (1, 2), y: 3 };

      match foo {
      Foo { x: (1, b), y } => println!("First of x is 1, b = {}, y = {} ", b, y),

      // you can destructure structs and rename the variables,
      // the order is not important
      Foo { y: 2, x: i } => println!("y is 2, i = {:?}", i),

      // and you can also ignore some variables:
      Foo { y, .. } => println!("y = {}, we don't care about x", y),
      // this will give an error: pattern does not mention field `x`
      //Foo { y } => println!("y = {}", y),
      }

函数

using the fn keyword,参数即跟类型声明一样,如果有返回值,则使用->

1
2
3
4
5
6
7
8
9
10
// Function that returns a boolean value
fn is_divisible_by(lhs: u32, rhs: u32) -> bool {
// Corner case, early return
if rhs == 0 {
return false;
}

// This is an expression, the `return` keyword is not necessary here
lhs % rhs == 0
}

Rust注重检查返回值,在C中,通常我们使用if else等判断 返回的ret,C++中可能会实现exception来catch error,而rust的函数则比较喜欢返回 Result这个枚举

1
2
3
4
enum Result<T, E> {
Ok(T),
Err(E),
}

其中T,E 可以是你指定的任意类型,然后通常会用match去处理返回的Result

1
2
3
4
5
6
7
8
9
10
use std::fs::File;

fn main() {
let f = File::open("hello.txt");

let f = match f {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
};
}

前面说过,match有destruted的功能,这里的返回值Result也是一个enum,其中的T和E,可以被destruted出来直接使用,很neat

类似的还有Option<T>

The Option<T> enum has two variants:

  • None, to indicate failure or lack of value, and
  • Some(value), a tuple struct that wraps a value with type T.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// An integer division that doesn't `panic!`
fn checked_division(dividend: i32, divisor: i32) -> Option<i32> {
if divisor == 0 {
// Failure is represented as the `None` variant
None
} else {
// Result is wrapped in a `Some` variant
Some(dividend / divisor)
}
}

// This function handles a division that may not succeed
fn try_division(dividend: i32, divisor: i32) {
// `Option` values can be pattern matched, just like other enums
match checked_division(dividend, divisor) {
None => println!("{} / {} failed!", dividend, divisor),
Some(quotient) => {
println!("{} / {} = {}", dividend, divisor, quotient)
},
}
}

fn main() {
try_division(4, 2);
try_division(1, 0);

// Binding `None` to a variable needs to be type annotated
let none: Option<i32> = None;
let _equivalent_none = None::<i32>;

let optional_float = Some(0f32);

// Unwrapping a `Some` variant will extract the value wrapped.
println!("{:?} unwraps to {:?}", optional_float, optional_float.unwrap());

// Unwrapping a `None` variant will `panic!`
println!("{:?} unwraps to {:?}", none, none.unwrap());
}

注意这个unwrap()用法,也很常用

Ownership and moves

ownership的规则如下:

  • Each value in Rust has a variable that’s called its owner.
  • There can only be one owner at a time.
  • When the owner goes out of scope, the value will be dropped.
1
2
3
4
5
{
let s = String::from("hello"); // s is valid from this point forward

// do stuff with s
} // this scope is now over, and s is no longer valid

moves即为ownership的转移,通常发生在赋值或传参时,如

let x = y表示将y 给了 x,ownership转移给了x,转移后,y就不能继续使用了

对于基本类型,赋值会发生copy,其它类型才会发生move

1
2
let x = 5;
let y = x; //copy, x,y都可以用
1
2
let a = String::new(); //在heap上创建了内存,a指向它
let b = a; //move, a无法acess前面创建的内存了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fn main() {
let s = String::from("hello"); // s comes into scope

takes_ownership(s); // s's value moves into the function...
// ... and so is no longer valid here

let x = 5; // x comes into scope

makes_copy(x); // x would move into the function,
// but i32 is Copy, so it’s okay to still
// use x afterward

} // Here, x goes out of scope, then s. But because s's value was moved, nothing
// special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
// memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.

Borrowing

move操作可发生在传参时,如

1
2
3
4
5
6
7
8
9
fn func(s: String) {
///use s do something
}// s outof scope, destroy

fn main() {
let s = String::from("test");
func(s); // s move到了func里面
//无法继续使用s了
}

所以很多时候我们要用一个值但又不想被拿走ownership,会使用borrorw操作,传值时传reference(&T)而不是 value(T)

1
2
3
4
5
6
7
8
9
fn func(s: &String) {
///use s
}

fn main() {
let s = String::from("test");
func(&s);
//s可以继续使用,内存没有被释放
}

Methods

使用impl关键字来定一个object的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Point {
x: f64,
y: f64,
}

// Implementation block, all `Point` methods go in here
impl Point {
// This is a static method
// Static methods don't need to be called by an instance
// These methods are generally used as constructors
fn origin() -> Point {
Point { x: 0.0, y: 0.0 }
}

// Another static method, taking two arguments:
fn new(x: f64, y: f64) -> Point {
Point { x: x, y: y }
}
}

let p1 = Point::origin();
let p2 = Point::new(3.0, 4.0);

另一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct Rectangle {
p1: Point,
p2: Point,
}

impl Rectangle {
// This is an instance method
// `&self` is sugar for `self: &Self`, where `Self` is the type of the
// caller object. In this case `Self` = `Rectangle`
fn area(&self) -> f64 {
// `self` gives access to the struct fields via the dot operator
let Point { x: x1, y: y1 } = self.p1;
let Point { x: x2, y: y2 } = self.p2;

// `abs` is a `f64` method that returns the absolute value of the
// caller
((x1 - x2) * (y1 - y2)).abs()
}

fn perimeter(&self) -> f64 {
let Point { x: x1, y: y1 } = self.p1;
let Point { x: x2, y: y2 } = self.p2;

2.0 * ((x1 - x2).abs() + (y1 - y2).abs())
}
}

let rectangle = Rectangle {
// Static methods are called using double colons
p1: Point::origin(),
p2: Point::new(3.0, 4.0),
};

// Instance methods are called using the dot operator
// Note that the first argument `&self` is implicitly passed, i.e.
// `rectangle.perimeter()` === `Rectangle::perimeter(&rectangle)`
println!("Rectangle perimeter: {}", rectangle.perimeter());
println!("Rectangle area: {}", rectangle.area());

rust的struct可以类比为C++的类,但很大的不同是:

C++的数据成员和成员方法都定义在类里面,而rust是分开的,struct只定义数据成员,它的方法定义在impl block body里面

可以理解为struct + impl methods实现了 一个 拥有特定成员和相应方法的object,就很像是C++的class了

Trait

A trait is a collection of methods defined for an unknown type: Self

Traits can be implemented for any data type.

类比于C++的interface

实现一个trait:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
pub trait Summary {
fn summarize(&self) -> String;
}

pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}

//给NewsArticle实现一个trait
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}

pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}

//给Tweet实现一个trait
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}

let tweet = Tweet { /*xxxx*/ };
println!("1 new tweet: {}", tweet.summarize());

trait可以定义default的函数,让实现它的type可以调用通用默认的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}

impl Summary for NewsArticle {}

fn main() {
let article = NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
};

println!("New article available! {}", article.summarize());
}
//结果输出为 New article available! (Read more...)

和methods的区别:

trait是定义了一组方法的一种type

然后被impl给其它data type,其它data type可以使用trait里的函数或overwrite成自己的定义的同名函数

而methods是实现为某个object的方法,而不是common的interface在多个type去共用的

Trait 实现rust的多态

trait 的impl –> 有继承的味道

一般面向对象语言如C++,通过继承来实现多态

举个栗子,一个GUI库,里面有多个component如按钮,图标,多选框,都有自己的draw函数来绘制自己的组件

所以一般会实现一个 component的父类,2然后 各个子组件再继承同个父类并实现自己的draw,然后由父类引用子类来调用它自己的draw

1
2
3
4
5
6
7
8
9
Component *com;
button but;
iamge img;

com = &but;
com->draw();//画按钮

com = &img;
com->draw();//画图形

多态可以做到,运行时,才确定com的引用是哪个,即运行时才会确定draw方法具体调用的哪个(运行时多态),这样可以实现出比较清晰的业务逻辑

rust的多态如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
pub trait Draw {
fn draw(&self);
}

//button
pub struct Button {
pub width: u32,
pub height: u32,
pub label: String,
}

impl Draw for Button {
fn draw(&self) {
//button自己的draw逻辑
}
}

//image
pub struct image {
//...image 自己的参数
}

impl Draw for Image {
fn draw(&self) {
//image自己的draw逻辑
}
}

//draw这个trait作为参数传入
fn draw_something(com: &dyn Draw) {
com.draw();
}

static dispatch vs dynamic dispatch

static dispath就是编译时就确定具体类型的,一般是模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// define an example struct, make it printable
#[derive(Debug)]
struct Foo;

// an example trait
trait Bar {
fn baz(&self);
}

// implement the trait for Foo
impl Bar for Foo {
fn baz(&self) {
println!("{:?}", self)
}
}

// This is a generic function that takes any T that implements trait Bar.
// It must resolve to a specific concrete T at compile time.
// The compiler creates a different version of this function
// for each concrete type used to call it so &T here is NOT
// a trait object (as T will represent a known, sized type
// after compilation)
fn static_dispatch<T>(t: &T)
where
T: Bar,
{
t.baz(); // we can do this because t implements Bar
}

fn func_bar(f: &Foo) {
f.baz();
}

fn main() {
let foo = Foo;

static_dispatch(&foo);//如果这里传的不是impl了Bar trait的,无法编译
//如果有其它impl了Bar的类型,编译器都会生成一个对应的函数

}

dynamic dispatch就是run time时才确定具体类型的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// This function takes a pointer to a something that implements trait Bar
// (it'll know what it is only at runtime). &dyn Bar is a trait object.
// There's only one version of this function at runtime, so this
// reduces the size of the compiled program if the function
// is called with several different types vs using static_dispatch.
// However performance is slightly lower, as the &dyn Bar that
// dynamic_dispatch receives is a pointer to the object +
// a vtable with all the Bar methods that the object implements.
// Calling baz() on t means having to look it up in this vtable.
fn dynamic_dispatch(t: &dyn Bar) {
// ----------------^
// this is the trait object! It would also work with Box<dyn Bar> or
// Rc<dyn Bar> or Arc<dyn Bar>
//
t.baz(); // we can do this because t implements Bar
}

fn main() {
let foo = Foo;
dynamic_dispatch(&foo);//即使传其它impl Bar的类型进来,也只有一个fn会被编译器生成
}

不管是static还是dynamic,你都可以做到传不同类型的输入,然后得到各自类型实现的输出

这里的区别在于最后生成的code size和speed,static因为每个模板都会被编译器输出,所以会有很多类似的copies,code size比dynamic大,而dynamic为了实现它的dynamic,它多了指针和vtable,性能会低一丢丢

以上是实现了某trait的struct作为参数传入funtion,如果我们想返回一个实现了某trait的struct呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
struct Sheep {}
struct Cow {}

trait Animal {
// Instance method signature
fn noise(&self) -> &'static str;
}

// Implement the `Animal` trait for `Sheep`.
impl Animal for Sheep {
fn noise(&self) -> &'static str {
"baaaaah!"
}
}

// Implement the `Animal` trait for `Cow`.
impl Animal for Cow {
fn noise(&self) -> &'static str {
"moooooo!"
}
}

// Returns some struct that implements Animal, but we don't know which one at compile time.
fn random_animal(random_number: f64) -> Box<dyn Animal> {
if random_number < 0.5 {
Box::new(Sheep {})
} else {
Box::new(Cow {})
}
}

//box的size是确定的两个usize,指向内存的pointer和对应的length
//编译时即知道,你要返回的是一个 pointer和len的组合,是一个长度为两个usize的东西
//如果直接 -> Animal
/*
fn random_animal(random_number: f64) -> Animal {
| ^^^^^^ doesn't have a size known at compile-time
*/

这部分还有很多设计方面的思想和细节我还没体会理解到,以后再讲~

Closures

Closures are functions that can capture the enclosing environment.

1
|val| val + x
  • using || instead of () around input variables.
  • optional body delimination ({}) for a single expression (mandatory otherwise).
  • the ability to capture the outer environment variables.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fn main() {
// Increment via closures and functions.
fn function (i: i32) -> i32 { i + 1 }

// Closures are anonymous, here we are binding them to references
// Annotation is identical to function annotation but is optional
// as are the `{}` wrapping the body. These nameless functions
// are assigned to appropriately named variables.
let closure_annotated = |i: i32| -> i32 { i + 1 };
let closure_inferred = |i | i + 1 ;

let i = 1;
// Call the function and closures.
println!("function: {}", function(i));
println!("closure_annotated: {}", closure_annotated(i));
println!("closure_inferred: {}", closure_inferred(i));

// A closure taking no arguments which returns an `i32`.
// The return type is inferred.
let one = || 1;
println!("closure returning one: {}", one());

}
  • 闭包就是在一个函数内创建立即调用的另一个函数。
  • 闭包是一个匿名函数。也就是没有函数名称。
  • 闭包虽然没有函数名,但可以把整个闭包赋值一个变量,通过调用该变量来完成闭包的调用。从某些方面说,这个变量就是函数名的作用。
  • 闭包不用声明返回值,但它却可以有返回值。并且使用最后一条语句的执行结果作为返回值。闭包的返回值可以赋值给变量。
  • 闭包有时候有些地方又称之为 内联函数。这种特性使得闭包可以访问外层函数里的变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fn function_name(parameters) -> return_type {
// 函数的具体逻辑
}

//闭包是一个没有函数名的内联函数
|parameter| {
// 闭包的具体逻辑
}
//闭包就是普通函数去掉 fn 关键字,去掉函数名,去掉返回值声明,并把一对小括号改成一对 竖线 ||

//闭包的参数是可选的,如果一个闭包没有参数,那么它的定义语法格式如下
||{
// 闭包的具体逻辑
}

//闭包虽然没有名称,但我们可以将闭包赋值给一个变量,然后就可以通过调用这个变量来完成闭包的调用
let closure_function = |parameter| {
// 闭包的具体逻辑
}
closure_function(parameter); //invoking

why use closure ?

  1. 减少重复代码
  2. 代码会变得简单易读( 熟练之后 :) )
  3. 很灵活,在迭代器里很好用
  4. ……

TEST

Tests are Rust functions that verify that the non-test code is functioning in the expected manner

To change a function into a test function, add #[test] on the line before fn

当执行cargo test时,rust 会编译一个可执行bin跑声明了test的函数,然后就可以看测试结果

1
2
3
4
5
6
7
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

当运行cargo test时,可以清楚知道运行结果,很方便做模块的单元测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.57s
Running target/debug/deps/adder-92948b65e88960b4

running 1 test
test tests::it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Doc-tests adder

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

并发

TODO

实际上async函数是由编辑器生成的future,await也是由编译器生成代码调用future的poll方法

Every await point is like a yield point.

Instead of yielding a value we pass in, we yield the result of calling poll on the next Future we’re awaiting.

资料

https://github.com/pretzelhammer/rust-blog/blob/master/posts/learning-rust-in-2020.md

https://fasterthanli.me/articles/a-half-hour-to-learn-rust 半小时过一遍大部分语法

https://github.com/rust-lang/rustlings 一组涵盖大部分语法和特性的半成品程序,让你去补充,使之编译test过,通过提示和编译器输出的error来做,做完会有大体的了解和熟悉

https://exercism.io/tracks/rust rust从易到难的习题,刷题的味道

https://doc.rust-lang.org/std/ 官方STD库,使用std提供的内容时,来这里查它的定义、方法、trait等

https://doc.rust-lang.org/rust-by-example/ 官方文档,绝大部分语法的example在这里面,不记得某个基础操作时来这里迅速找例子

https://doc.rust-lang.org/stable/book/title-page.html The Book, 官方圣经级别,不好读