September 1, 2023
By: Kevin

rust和clojure对比

  1. 使用场景
  2. 工具链
  3. 创建一个新项目
  4. 模块
  5. 模块(箱子 crates)
  6. 类型
  7. 引用
  8. 不可变性(Mutability)
  9. 可空性(Nullability)
  10. 模式匹配(attern Matching)
  11. 表达式(Expressions)
  12. 宏(Macro)

使用场景

喜欢Clojure的原因

  1. 表现力
  2. 借助现有生态(JVM或者JS)
  3. 并发编程

clojure的缺点

  1. 性能(比动态语言快,不如没有运行时的编译语言(C/C++), 而且有GC带来的不可预测性)
  2. 安全性(高敏感性, 飞行器控制, 心脏起搏器)
  3. 可嵌入(资源极度受限场景)

clojure的优势是rust的劣势, 反之也成立. Rust没有非常强的表现力(当然, 比c/c++要强不少), 也没有对于不可变性和并发的语言层面的支持, 但是它对于性能或安全关键需求要好得多, 并且可以嵌入到其他程序中或非常有限的硬件上.

这些特性决定了Rust是一门高性能/高安全性的系统编程语言. 当前(2023年)来看它 有望成为Linux/Windows上的操作系统开发语言.

工具链

clojure一般以lein为起点, project.clj为配置文件,通过既有的Maven和clojar.org的托管库.搭建整个项目. rust以cargo为起点, 通过配置Cargo.toml, 指定托管在cartes.io的库, 搭建Rust项目.

创建一个新项目

lein和cargo分别是clojure和rust的工程管理工具, 可以用于项目的初始化. 在clojure侧, clojure自带的clojure cli和lein存在竞争关系, 从新人入手角度, lein当前(2023年)还是可以优先选择的.

  • clojure lein new app hello-world

  • rust cargo new hello_world --bin 如果省略“–bin”,它将创建一个库.

模块

虽然Rust语言本身不包含命名空间声明, 单一旦项目有多个个源文件,且有引用和包含的关系. 我们就需要有命名空间的概念. 有别于C/C++/clojure, rust只能包含文件. Rust将代码分离到模块中,每个源文件根据文件名自动给出一个模块名. 我们可以像这样在单独的文件中写一个函数, 在另一个文件中使用 mod file-name 来说明文件名 file-name 就是一个模块名.

// utils.rs
pub fn say_hello() {
    println!("Hello, world!");
}

pub fn say_goodbye() {
    println!("Goodbye, world!");
}

// main.rs
mod utils;

fn main() {
    utils::say_hello();
    utils::say_goodbye();
}

Rust的mod与Clojure的ns相似, 它创建了一个模块,但是它位于引用文件 main.rs 的顶部,而不是模块文件本身. 自此, 在被引用处,我们只需在函数名称之前将utils::添加到使用它们. 它们是用 pub 声明的. 与Clojure不同,Rust默认将函数设为私有.

Rust的use类似于Clojure的require, 因为它引入了一个现有的模块, 并且暴露其中的部分函数. 这是一个略微修改的main.rs, 我们明确地引入了符号, 因此我们不需要别名它, 就像Clojure的 require 结合 :refer 关键字起到的作用一样.

// main.rs
use utils::{say_hello, say_goodbye};

mod utils;

fn main() {
    say_hello();
    say_goodbye();
}

模块(箱子 crates)

众所周知, 具有自己软件包管理器的语言通常具有自己独特名称的库的特殊格式. Python(蟒蛇)有它的"蛋(egg)",Ruby(红宝石)有它的“宝石(gem)”. Clojure位于现有生态系统(JVM)中, 因此它无法发明自己的格式.它使用与其他JVM语言相同的无聊“jar(包)”

Rust选择称其格式为 crates 要使用 crates 可以将其添加到Cargo.toml中,就像使用project.clj一样。以下是我添加 时间 库后的样子:

[package]

name = "hello_world"
version = "0.0.1"
authors = ["oakes <kevinli@gmail.com>"]

[dependencies.time]

time = "0.1.2"

和其他模块一样, 可以使用 use 使用它

// main.rs
extern crate time;

use utils::{say_hello, say_goodbye};

mod utils;

fn main() {
    say_hello();
    say_goodbye();
}

类型

到目前为止,我们一直避免提到类型,因为我们的函数都不接受参数. Rust是静态类型, 好处是编译期间可以更多发现错误. 缺点是说服编译器去尝试一个想法会更加费力一些. 接下来修改一下函数,将字符串"你好"或再见"作为参数传递:

// main.rs

extern crate time;

use utils::say_something;

mod utils;

fn main() {
    say_something("Hello");
    say_something("Goodbye");
}
// utils.rs

use time;

pub fn say_something(word: &str) {
    let t = time::now();
    println!("{}, world at {}!", word, t.asctime());
}

Rust参数的语法类似于ML, 其中名称首先出现, 然后是冒号, 然后是类型. 在Rust中, 静态分配的字符串具有 &str 的类型, 称为"string slice". 堆分配的字符串具有String的类型, 这是在Clojure或其他高级语言中找不到对应.

我们还使用let将时间对象移动到局部变量中, 这对Clojure用户来说应该是熟悉的. 在Rust中, 需要指定顶级函数的类型,但几乎从不指定局部变量. Rust有类型推断, 因此它可以自行计算出 t 的类型(Tm)

正如 Tm 文档所示, asctime函数返回一个 TmFmt. 虽然 println! 不知道那是什么, 但是没关系. 它实现了一个类似于Clojure中protocol,叫 Trait, 称为Display,这就是所有 println! 需要的全部信息. Trait 在Rust中普遍使用. 阅读文档以了解更多信息.

引用

在以往的编程语言设计中, 精细的内存管理和安全编程之间是二选一的关系. 第一类: 在大多数高级语言(Python, clojure, Java)中, 内存在heap或者stack分配是无法控制的. 第二类: 在C/C++中, 程序对此是有完全掌控的, 代价是更大的出错可能性. 引用是Rust的高光特性, 给到了C/C++的完全底层控制和高级语言安全性.

Rust中, 不仅仅是内存分配的完全控制, 函数参数传递的方式也是完全可控的. 在此也有别于第一类高级语言, 在高级语言中给函数传入的参数到底是引用传入还是值传递是语言本身决定的. 这就是 &str& 的含义. 字面字符串会自动以引用的方式传递. 但是对于普通变量而言, 所有的变量都是以值, 如果要以引用的方式来传递, 必须加上 & 开头.

举例来说, Tm 以引用方式传入.

// main.rs

extern crate time;

use utils::say_something;

mod utils;

fn main() {
    let t = time::now();
    say_something("Hello", &t);
    say_something("Goodbye", &t);
}
// utils.rs

use time;

pub fn say_something(word: &str, t: &time::Tm) {
    println!("{}, world at {}!", word, t.asctime());
}

不可变性(Mutability)

// main.rs

extern crate time;

use utils::say_something;

mod utils;

fn main() {
    let mut t = time::now();
    t.clone_from(&time::now_utc());
    say_something("Hello", &t);
    say_something("Goodbye", &t);
}


// main.rs

extern crate time;

use utils::say_something;

mod utils;

fn main() {
    let mut t = time::now();
    say_something("Hello", &mut t);
    say_something("Goodbye", &mut t);
}

// utils.rs

use time;

pub fn say_something(word: &str, t: &mut time::Tm) {
    t.clone_from(&time::now_utc());
    println!("{}, world at {}!", word, t.asctime());
}

可空性(Nullability)

// main.rs

extern crate time;

use utils::say_something;

mod utils;

fn main() {
    let t = time::now();
    say_something("Hello", Some(&t));
    say_something("Goodbye", None);
}
// utils.rs

use time;

pub fn say_something(word: &str, t: Option<&time::Tm>) {
    if t.is_some() {
        println!("{}, world at {}!", word, t.unwrap().asctime());
    } else {
        println!("{}, world at {}!", word, time::now_utc().asctime());
    }
}

模式匹配(attern Matching)

// utils.rs

use time;

pub fn say_something(word: &str, t: Option<&time::Tm>) {
    if let Some(t_ptr) = t {
        println!("{}, world at {}!", word, t_ptr.asctime());
    } else {
        println!("{}, world at {}!", word, time::now_utc().asctime());
    }
}

表达式(Expressions)

clojure(lisp)中, 所有的结构都是表达式

// utils.rs

use time;

pub fn say_something(word: &str, t: Option<&time::Tm>) {
    let t_val = if let Some(t_ptr) = t {
        *t_ptr
    } else {
        time::now_utc()
    };
    println!("{}, world at {}!", word, t_val.asctime());
}

// utils.rs

use time;

pub fn say_something(word: &str, t: Option<&time::Tm>) -> time::Tm {
    let t_val = if let Some(t_ptr) = t {
        *t_ptr
    } else {
        time::now_utc()
    };
    println!("{}, world at {}!", word, t_val.asctime());
    t_val
}

宏(Macro)

rust中函数带 ! 的意思是编译期间的宏函数. rust中的宏和C/C++的是一样的文本替换(提供了更多的安全检查). 不具有lisp系统中的灵活性.

Tags: clojure