标签 gomodule 下的文章

Gopher的Rust第一课:Rust代码组织

本文永久链接 – https://tonybai.com/2024/06/06/gopher-rust-first-lesson-organizing-rust-code

在上一章的讲解中,我们编写了第一个Rust示例程序”hello, world”,并给出了rustc版和cargo版本。在真实开发中,我们都会使用cargo来创建和管理Rust包。不过,Hello, world示例非常简单,仅仅由一个Rust源码文件组成,而且所有源码文件都在同一个目录中。但真实世界中的实用Rust程序,无论是公司商业项目,还是一些知名的开源项目,甚至是一些稍复杂一些的供教学使用的示例程序,它们通常可不会这么简单,都有着复杂的代码结构。

Rust初学者在阅读这些项目源码时便仿佛进入了迷宫,不知道该走哪条(阅读代码的)路径,不知道每个目录代表的含义,也不知道自己想看的源码究竟在哪个目录下。但目前市面上的Rust入门教程大多没有重视初学者的这一问题,要么没有对Rust项目代码组织结构进行针对性的讲解,要么是将讲解放到书籍的后面章节。

根据我个人的学习经验来看,理解一个实用Rust项目的代码组织结构越早,对后续的Rust学习越有益处。同时,掌握Rust项目的代码组织结构也是Rust开发者走向编写复杂Rust程序的必经的一步。并且,初学者在了解项目的代码组织结构后,便可以自主阅读一些复杂的Rust项目的源码,可提高Rust学习的效率,提升学习效果。因此,我决定在介绍Rust基础语法之前先在本章中系统地介绍Rust的代码组织结构,以满足很多Rust初学者的述求。

但在介绍Rust代码组织结构之前,我们需要先来系统说明一下Rust代码组织结构中的几个重要概念,它们是了解Rust项目代码组织结构的前提。

4.1 回顾Go代码组织

Go项目代码组织由module和package两级组成。通常来说,每个Go repo就是一个module,由repo根目录下的go.mod定义,go.mod文件所在目录也被称为module root。go.mod中典型内容如下:

// go.mod
module github.com/user/mymodule[/vN]

go 1.22.1

... ...

go.mod中的module directive一行后面的github.com/user/mymodule/[vN]是module path。module path一来可以反映该module的具体网络位置,同时也是该module下面的Go package导入(import)路径的组成部分。module root下的子目录中通常存放着该module下面的Go package,比如module root/foo目录下存放的Go包的导入路径为github.com/user/mymodule[/vN]/foo。

Go package是Go的编译单元,也是功能单元,代码内外部导入和引用的单位也都是包。而go module是后加入的,更多用于管理包的版本(一个module下的所有包都统一进行版本管理)以及构建时第三方依赖和版本的管理。

更多关于Go module和package管理以及Go项目布局的内容,可以详见我的极客时间《Go语言第一课》专栏。

个人认为Go的module和package的两级管理还是很好理解和管理的,在这方面Rust的代码组织形式又是怎样的呢?接下来,我们就来正式看看Rust的代码组织。

4.2 rustc-only的Rust项目

Rust是系统编程语言,这让我想起了当初在Go成为我个人主力语言之前使用C/C++进行开发的岁月。C/C++是没有像go或Rust的cargo那样的统一的包依赖管理器和项目构建管理工具的。编译器(如gcc等)是核心工具,而项目构建管理则经常由其他工具负责,如Makefile、CMake,或者是Google的Bazel等。在Windows上开发应用的,则往往使用微软或其他开发者工具公司提供的IDE,如当年炙手可热的Visual Studio系列。

下面表格展示了各语言的编译器/链接器和构建管理工具的关系:

像cargo、go这样的“一站式”工具链都旨在为开发者提供体验更为友好的交互接口的,在幕后,它们仍然依赖于底层的编译器和链接器(如rustc和go tool compile/link)来执行实际的代码编译。

不过,像cargo这样的高级工具也给开发人员带来了额外的抽象,或是叫“掩盖”了一些真相,这有时候让人看不清构建过程的本质,比如:很多Gopher用了很多年Go,但却不知道go tool compile/link的存在。

本着只有in hard way,才能看到和抓住本质的思路,以及之前学习用系统编程语言C/C++时经验,这里我们先来看一些rustc-only的Rust项目。Rustc-only的Rust项目是指不使用Cargo创建和管理的Rust项目,而是直接使用rustc编译器来编译和构建项目。这意味着开发者需要编写自己的构建脚本,例如使用Makefile或其他构建工具来管理项目的构建过程。

不过,请注意:这类项目极少用于生产,即便是那些不需要复杂的依赖管理的小型项目。这里使用rustc-only的Rust项目仅仅是为了学习和了解Rustc编译器的主要功能机制以及Rust语言在代码组织上的一些抽象,比如module等。

下面我们就从最简单的rustc-only项目开始,先来看看只有一个Rust源文件且无其他依赖项的“最简项目”。

4.2.1 单文件项目

所谓单文件项目,即只有一个Rust源文件,例如前面章节中的hello_world.rs,这种项目可以直接使用rustc编译器来编译和运行:

// rust-guide-for-gopher/organizing-rust-code/rustc-only/single/hello-world/hello_world.rs
fn main() {
    println!("Hello, world!");
}

对于顶层带有main函数的源文件,rustc会默认将其视为binary crate类型的源文件,并将其编译为可执行二进制文件hello_world。

我们当然也可以强制的让rustc将该源文件视为library crate类型的源文件,并将其编译为其他类型的crate输出文件,rustc支持多种crate type:

      --crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
                        Comma separated list of types of crates
                        for the compiler to emit

rustc的文档中,各种crate类型的含义如下:

lib — Generates a library kind preferred by the compiler, currently defaults to rlib.
rlib — A Rust static library.
staticlib — A native static library.
dylib — A Rust dynamic library.
cdylib — A native dynamic library.
bin — A runnable executable program.
proc-macro — Generates a format suitable for a procedural macro library that may be loaded by the compiler.

不过,如果强制将带有顶层main函数的rust源文件视为lib crate型的,那么rustc将会报warning,提醒你函数main将是死代码,永远不会被用到:

$rustc --crate-type lib hello_world.rs
warning: function `main` is never used
 --> hello_world.rs:1:4
  |
1 | fn main() {
  |    ^^^^
  |
  = note: `#[warn(dead_code)]` on by default

warning: 1 warning emitted

但即便如此,一个名为libhello_world.rlib的文件依然会被rustc生成出来!(目前–crate-type lib等同于–create-type rlib)。

4.2.2 有外部依赖项的单文件项目

日常开发中,像上面的Hello, World级别的trivial应用是极其少见的,一个non-trivial的Rust应用或多或少都会有一些依赖。这里我们也来看一下如何基于rustc来构建带有外部依赖的单文件项目。下面是一个带有外部依赖的示例:

// organizing-rust-code/rustc-only/single/hello-world-with-deps/hello_world.rs
extern crate rand;

use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    let num: u32 = rng.gen();
    println!("Random number: {}", num);
}

这个示例程序依赖一个名为rand的crate,要编译该程序,我们必须先手动下载rand的crate源码,并在本地将rand源码编译为示例程序所需的rust library。下面步骤展示了如何下载和构建rand crate:

$curl -LO https://crates.io/api/v1/crates/rand/0.8.5/download
$tar -xvf download

解压后,我们将看到rand-0.8.5这样的一个crate目录,进入该目录,我们执行cargo build来构建rand crate:

$cd rand-0.8.5
$cargo build
... ...
   Finished dev [unoptimized + debuginfo] target(s) in 0.19s

cargo构建出的librand.rlib就在rand-0.8.5/target/debug下。

注:rlib的命名方式:lib+{crate_name}.rlib

接下来,我们就来构建一下依赖rand crate的hello_world.rs:

// 在organizing-rust-code/rustc-only/single/hello-world-with-deps下面执行

$rustc --verbose  -L ./rand-0.8.5/target/debug  --extern rand=librand.rlib hello_world.rs
error[E0463]: can't find crate for `rand_core` which `rand` depends on
 --> hello_world.rs:1:1
  |
1 | extern crate rand;
  | ^^^^^^^^^^^^^^^^^^ can't find crate

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0463`.

我们看到rustc的编译错误提示:无法找到rand crate依赖的rand_core crate!也就是说我们除了向rustc提供hello_world.rs依赖的rand crate之外,还要向rustc提供rand crate的各种依赖!

rand crate的各种依赖在哪里呢?我们在构建rand crate时,cargo build将各种依赖都放在了rand-0.8.5/target/debug/deps目录下了:

$ls -l|grep ".rlib"
-rw-r--r--   1 tonybai  staff     6896  4 29 06:45 libcfg_if-cd6bebf18fb9c234.rlib
-rw-r--r--   1 tonybai  staff   204072  4 29 06:45 libgetrandom-df6a8e95e188fc56.rlib
-rw-r--r--   1 tonybai  staff  1651320  4 29 06:45 liblibc-f16531562d07b476.rlib
-rw-r--r--   1 tonybai  staff   959408  4 29 06:45 libppv_lite86-f1d97d485bc43617.rlib
-rw-r--r--   1 tonybai  staff  1784376  4 29 06:45 librand-9a91ea8db926e840.rlib
-rw-r--r--   1 tonybai  staff   987936  4 29 06:45 librand_chacha-6fe22bd8b3bb228c.rlib
-rw-r--r--   1 tonybai  staff   256768  4 29 06:45 librand_core-fc905f6ca5f8533b.rlib

我们看到其中还包含了librand自身:librand-9a91ea8db926e840.rlib。我们来试试基于deps目录下的这些依赖rlib编译一下:

$rustc --verbose  --extern rand=rand-0.8.5/target/debug/deps/librand-9a91ea8db926e840.rlib -L rand-0.8.5/target/debug/deps  --extern rand_core=librand_core-fc905f6ca5f8533b.rlib --extern getrandom=libgetrandom-df6a8e95e188fc56.rlib --extern cfg_if=libcfg_if-cd6bebf18fb9c234.rlib --extern libc=liblibc-f16531562d07b476.rlib --extern rand_chacha=librand_chacha-6fe22bd8b3bb228c.rlib --extern ppv_lite86=libppv_lite86-f1d97d485bc43617.rlib  hello_world.rs

我们用rustc成功编译了带有外部依赖的Rust源码。不过这里要注意的是rustc对直接依赖和间接依赖的crate的定位方式有所不同。

对于直接依赖的crate,比如这里的rand crate,我们需要给出具体路径,它不依赖-L的位置指示,所以这里我们使用了–extern rand=rand-0.8.5/target/debug/deps/librand-9a91ea8db926e840.rlib。

对于间接依赖的crate,比如rand crate依赖的rand_core,rust会结合-L指示的位置以及–extern一起来定位,这里-L指示路径为rand-0.8.5/target/debug/deps,–extern rand_core=librand_core-fc905f6ca5f8533b.rlib,那么rustc就会在rand-0.8.5/target/debug/deps下面搜索librand_core-fc905f6ca5f8533b.rlib是否存在。

我们运行rustc构建出的可执行文件,输出如下:

$./hello_world
Random number: 431751199

4.2.3 有外部依赖的多文件项目

在Go中,如果某个目录下有多个源文件,那么通常这几个源文件均归属于同一个Go包(可能的例外的是*_test.go文件的包名)。但在Rust中,情况就会变得复杂了一些,我们来看一个例子:

// organizing-rust-code/rustc-only/multi/multi-file-with-deps

$tree -F -L 2
.
├── main.rs
├── sub1/
│   ├── bar.rs
│   ├── foo.rs
│   └── mod.rs
└── sub2.rs

在这个示例中,我们看到除了main.rs之外,还有一个sub2.rs以及一个目录sub1,sub1下面还有三个rs文件。我们从main.rs开始,逐一看一下各个源文件的内容:

// organizing-rust-code/rustc-only/multi/multi-file-with-deps/main.rs
 1 extern crate rand;
 2 use rand::Rng;
 3
 4 mod sub1;
 5 mod sub2;
 6
 7 mod sub3 {
 8     pub fn func1() {
 9         println!("called {}::func1()", module_path!());
10     }
11     pub fn func2() {
12         self::func1();
13         println!("called {}::func2()", module_path!());
14         super::func1();
15     }
16 }
17
18 fn func1() {
19     println!("called {}::func1()", module_path!());
20 }
21
22 fn main() {
23     println!("current module: {}", module_path!());
24     let mut rng = rand::thread_rng();
25     let num: u32 = rng.gen();
26     println!("Random number: {}", num);
27
28     sub1::func1();
29     sub2::func1();
30     sub3::func2();
31 }

在main.rs中,我们除了看到了第1~2行的对外部rand crate的依赖外,我们还看到了一种新的语法元素:rust module。这里涉及sub1~sub3三个module,我们分别来看一下。先来看一下最直观的、定义在main.rs中的sub3 module。

第7行~第16行的代码定义了一个名为sub3的module,它包含两个函数func1和func2,这两个函数前面的pub关键字表明他们是sub3 module的publish函数,可以被module之外的代码所访问。任何未标记为pub的函数都是私有的,只能在模块内部及其子模块中使用。

在sub3 module的func2函数中,我们调用了self::func1()函数,self指代是模块自身,因此这个self::func1()函数就是sub3的func1函数。而接下来调用的super::func1()调用的语义你大概也能猜到。super指代的是sub3的父模块,而super::func1()就是sub3的父模块中的func1函数。

sub3的父模块就是这个项目的顶层模块,我们在main函数的入口处使用module_path!宏输出了该顶层模块的名称。

和sub3在main.rs中定义不同,sub1和sub2也分别代表了另外两种module的定义方式。

当Rust编译器看到第4行mod sub1后,它会寻找当前目录下是否有名为sub1.rs的源文件或是sub1/mod.rs源文件。在这个示例中,sub1定义在sub1目录下的mod.rs中:

// organizing-rust-code/rustc-only/multi/multi-file-with-deps/sub1/mod.rs

pub mod bar;
pub mod foo;

pub fn func1() {
    println!("called {}::func1()", module_path!());
    foo::func1();
    bar::func1();
}

我们看到sub1/mod.rs中定义了一个公共函数func1,同时也在最开始处又嵌套定义了bar和foo两个module,并在func1中调用了两个嵌套子module的函数:

bar和foo两个module都是使用单文件module定义的,编译器会在sub1目录下搜寻foo.rs和bar.rs:

// organizing-rust-code/rustc-only/multi/multi-file-with-deps/sub1/foo.rs
pub fn func1() {
    println!("called {}::func1()", module_path!());
}

// organizing-rust-code/rustc-only/multi/multi-file-with-deps/sub1/bar.rs
pub fn func1() {
    println!("called {}::func1()", module_path!());
}

而main.rs中的sub2也是一个单文件的module,其源码位于顶层目录下的sub2.rs文件中:

// organizing-rust-code/rustc-only/multi/multi-file-with-deps/sub2.rs
pub fn func1() {
    println!("called {}::func1()", module_path!());
}

现在我们来编译和执行一下这个既有外部依赖,又是多文件且有多个module的rustc-only项目:

$rustc --verbose  --extern rand=rand-0.8.5/target/debug/deps/librand-9a91ea8db926e840.rlib -L rand-0.8.5/target/debug/deps  --extern rand_core=librand_core-fc905f6ca5f8533b.rlib --extern getrandom=libgetrandom-df6a8e95e188fc56.rlib --extern cfg_if=libcfg_if-cd6bebf18fb9c234.rlib --extern libc=liblibc-f16531562d07b476.rlib --extern rand_chacha=librand_chacha-6fe22bd8b3bb228c.rlib --extern ppv_lite86=libppv_lite86-f1d97d485bc43617.rlib  main.rs 

$./main
current module: main
Random number: 2691905579
called main::sub1::func1()
called main::sub1::foo::func1()
called main::sub1::bar::func1()
called main::sub2::func1()
called main::sub3::func1()
called main::sub3::func2()
called main::func1()

上面示例演示了三种rust module的定义方法:

  1. 直接将定义嵌入在某个rust源文件中:
mod module_name {

}
  1. 通过module_name.rs
  2. 通过module_name/mod.rs

在一个单crate的项目中,通过rust module可以满足项目内部代码组织的需要。

最后,我们再来看一个有多个crate的项目形式。

4.2.4 有多个crate的项目

下面是一个有着多个crate项目的示例:

// organizing-rust-code/rustc-only/workspace

$tree -L 2 -F
.
├── main.rs
├── my_local_crate1/
│   └── lib.rs
└── my_local_crate2/
    └── lib.rs

在这个示例中有三个crate,一个是顶层的binary类型的crate,入口为main.rs,另外两个都是lib类型的crate,入口都在lib.rs中,我们贴一下他们的源码:

// organizing-rust-code/rustc-only/workspace/main.rs
extern crate my_local_crate1;
extern crate my_local_crate2;

fn main() {
    let x = 5;
    let y = my_local_crate1::add_one(x);
    let z = my_local_crate2::multiply_two(y);
    println!("Result: {}", z);
}

// organizing-rust-code/rustc-only/workspace/my_local_crate1/lib.rs
pub fn add_one(x: i32) -> i32 {
    x + 1
}

// organizing-rust-code/rustc-only/workspace/my_local_crate2/lib.rs
pub fn multiply_two(x: i32) -> i32 {
    x * 2
}

要构建这个带有三个crate的项目,我们需要首先编译my_local_crate1和my_local_crate2这两个lib crates:

$rustc --crate-type lib --crate-name my_local_crate1 my_local_crate1/lib.rs
$rustc --crate-type lib --crate-name my_local_crate2 my_local_crate2/lib.rs

这会在项目顶层目录下生成两个rlib文件:

$ls  |grep rlib
libmy_local_crate1.rlib
libmy_local_crate2.rlib

之后,我们就可以用之前学到的方法编译binary crate了:

$rustc --extern my_local_crate1=libmy_local_crate1.rlib --extern my_local_crate2=libmy_local_crate2.rlib main.rs

上述的几个rustc-only的rust项目都是hard模式的,即一切都需要手工去做,包括下载crate、编译crate时传入各种路径等。在真正的生产中,Rustacean们是不会这么做的,而是会直接使用cargo对rust项目进行管理。接下来,我们就来系统地看一下使用cargo进行rust项目管理以及对应的rust代码组织形式。

4.3 使用cargo管理的Rust项目

在前面的章节中,我们见识过了:Rust的包管理器Cargo是一个强大的工具,可以帮助我们轻松地管理Rust项目,cargo才是生产类项目的项目构建管理工具标准,它可以让Rustacean避免复杂的手工rustc操作。Cargo提供了许多功能,包括依赖项管理、构建和测试等。不过在这篇文章中,我不会介绍这些功能,而是看看使用cargo管理的Rust项目都有哪些代码组织模式。

Rust项目的代码组织结构可以分为两类:单一package和多个package。

什么是package?在之前的rust-only项目中,我们可从未见到过package!package是cargo引入的一个管理单元概念,它指的是一个独立的Rust项目,包含了源代码、依赖项和配置信息。每个Package都有一个唯一的名称和版本号,用于标识和管理项目。因此,在the cargo book中,cargo也被称为“Rust package manager”,crates.io也被称为“the Rust community’s package registry”。

最能直观体现package存在的就是下面Cargo.toml中的配置了:

[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"

[dependencies]

下面我们就来看看不同类型的rust package的代码组织形式。我们先从单一package形态的项目来开始。

4.3.1 单一package的rust项目

单一package项目是指整个项目只有一个Cargo.toml文件。这种项目还可以进一步分为三类:

  1. 单一Binary Crate
  2. 单一Library Crate
  3. 多个Binary Crate和一个Library Crate

下面我们分别举例来说明一下这三类项目。

4.3.1.1 单一Binary Crate

我们进入organizing-rust-code/cargo/single-package/single-binary-crate,然后执行下面命令来创建一个单一Binary Crate的项目:

$cargo new hello_world --bin
     Created binary (application) `hello_world` package

这个例子我们在之前的章节中也是见过的,它的结构如下:

$tree hello_world
hello_world
├── Cargo.toml
└── src
    └── main.rs

1 directory, 2 files

默认生成的Cargo.toml内容如下:

[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

使用cargo build即可完成该项目的构建:

$cargo build
   Compiling hello_world v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/single-package/single-binary-crate/hello_world)
    Finished dev [unoptimized + debuginfo] target(s) in 1.16s

为了更显式地体现这是一个binary crate,我们可以在Cargo.toml增加如下内容:

[[bin]]
name = "hello_world"
path = "src/main.rs"

这不会影响cargo的构建结果!

通过cargo run可以查看构建出的可执行文件的运行结果:

$cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.06s
     Running `target/debug/hello_world`
Hello, world!

接下来,我们再来看看单一library crate的rust项目。

4.3.1.2 单一Library Crate

我们进入organizing-rust-code/cargo/single-package/single-library-crate,然后执行下面命令来创建一个单一Library Crate的项目:

$cargo new my_library --lib
     Created library `my_library` package

创建后的my_library项目的结构如下:

$tree
.
├── Cargo.toml
└── src
    └── lib.rs

默认生成的Cargo.toml如下:

[package]
name = "my_library"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

和binary crate的一样,我们也可以显式指定target:

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

注意,这里是[lib]而不是[[lib]],这是因为在一个carge package中最多只能存在一个library crate,但binary crate可以有多个。

接下来,我们就看看一个由多个binary crate和一个library crate混合构成的rust项目。

4.3.1.3 多个Binary Crate和一个Library Crate

我们在organizing-rust-code/cargo/single-package/hybrid-crates下面执行如下命令创建这个多crates混合项目:

$cargo new my_project
     Created binary (application) `my_project` package

上述命令默认创建了一个binary crate的project,我们需要配置一下Cargo.toml,将其改造为多个crates并存的project:

[package]
name = "my_project"
version = "0.1.0"
edition = "2021"

[[bin]]
name = "cmd1"
path = "src/main1.rs"

[[bin]]
name = "cmd2"
path = "src/main2.rs"

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

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]

这里定义了三个crates。两个binary crates: cmd1、cmd2以及一个library crate:my_library。

如果我们执行cargo build,cargo会将三个crate都构建出来:

$cargo build
   Compiling my_project v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/single-package/hybrid-crates/my_project)
    Finished dev [unoptimized + debuginfo] target(s) in 0.80s

我们可以在target/debug下找到构建出的crates:cmd1、cmd2和libmy_library.rlib:

$ls target/debug
build/          cmd1.d          cmd2.d          examples/       libmy_library.d
cmd1*           cmd2*           deps/           incremental/        libmy_library.rlib

我们也可以通过cargo分别运行两个binary crate:

$cargo run --bin cmd1
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/cmd1`
cmd1

$cargo run --bin cmd2
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/cmd2`
cmd2

4.3.1.4 典型的cargo package

在The cargo book中,有一个典型的cargo package的示例:

.
├── Cargo.lock
├── Cargo.toml
├── src/
│   ├── lib.rs
│   ├── main.rs
│   └── bin/
│       ├── named-executable.rs
│       ├── another-executable.rs
│       └── multi-file-executable/
│           ├── main.rs
│           └── some_module.rs
├── benches/
│   ├── large-input.rs
│   └── multi-file-bench/
│       ├── main.rs
│       └── bench_module.rs
├── examples/
│   ├── simple.rs
│   └── multi-file-example/
│       ├── main.rs
│       └── ex_module.rs
└── tests/
    ├── some-integration-tests.rs
    └── multi-file-test/
        ├── main.rs
        └── test_module.rs

在这样一个典型的项目中:

  • Cargo.toml和Cargo.lock文件存储在包的根目录(包根目录)中。
  • 源代码位于src目录中。
  • 默认的库文件是src/lib.rs。
  • 默认的可执行文件是src/main.rs。
  • 其他可执行文件可以放在src/bin/目录中。
  • 基准测试位于benches目录中。
  • 示例位于examples目录中。
  • 集成测试位于tests目录中。

4.3.2 多package的rust项目

一些中大型的Rust项目都是多package的,比如rust的异步编程事实标准tokio库、刚刚升级为Apache基金会顶级项目的SQL查询引擎datafusion等。以tokio为例,这些项目的顶层Cargo.toml都是这样的:

// https://github.com/tokio-rs/tokio/blob/master/Cargo.toml
[workspace]
resolver = "2"
members = [
  "tokio",
  "tokio-macros",
  "tokio-test",
  "tokio-stream",
  "tokio-util",

  # Internal
  "benches",
  "examples",
  "stress-test",
  "tests-build",
  "tests-integration",
]

[workspace.metadata.spellcheck]
config = "spellcheck.toml"

上面这个Cargo.toml示例与我们在前面见到的Cargo.toml都不一样,它并不包含package配置,其主要的配置为workspace。我们看到workspace的members字段中配置了该项目下的其他package。正是通过这个配置,cargo可以在一个项目里管理和构建多个package。

工作空间(Workspace)是一组一个或多个包(Package)的集合,这些包称为工作空间成员(Workspace Members),它们一起被管理。接下来,我们就来创建一个多package的cargo项目。

4.3.2.1 cargo管理的多package项目

由于cargo并没有提供cargo new my-pakcage –workspace这样的命令行参数,项目的顶层Cargo.toml需要我们手动创建和编辑。

$cd organizing-rust-code/cargo/multi-packages
$mkdir my-workspace
$cd my-workspace
$cargo new package1 --bin
     Created binary (application) `package1` package
$cargo new package2 --lib
     Created library `package2` package
$cargo new package3 --lib
     Created library `package3` package

接下来,我们手工创建和编辑一下项目顶层的Cargo.toml如下:

// organizing-rust-code/cargo/multi-packages/my-workspace/Cargo.toml
[workspace]
resolver = "2"
members = [
    "package1",
    "package2",
    "package3",
]

保存后,我们可以在项目顶层目录下使用下面命令检查整个工作空间(workspace)中的所有包(package),确保它们的代码正确无误,不包含任何编译错误:

$cargo check --workspace
    Checking package1 v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/multi-packages/my-workspace/package1)
    Checking package2 v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/multi-packages/my-workspace/package2)
    Checking package3 v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/multi-packages/my-workspace/package3)
    Finished dev [unoptimized + debuginfo] target(s) in 0.18s

在顶层目录执行cargo build,cargo会build工作空间中的所有package:

$cargo build
   Compiling package3 v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/multi-packages/my-workspace/package3)
   Compiling package2 v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/multi-packages/my-workspace/package2)
   Compiling package1 v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/multi-packages/my-workspace/package1)
    Finished dev [unoptimized + debuginfo] target(s) in 0.64s

构建后,该项目的目录结构变成下面这个样子:

$tree -L 2 -F
.
├── Cargo.lock
├── Cargo.toml
├── package1/
│   ├── Cargo.toml
│   └── src/
├── package2/
│   ├── Cargo.toml
│   └── src/
├── package3/
│   ├── Cargo.toml
│   └── src/
└── target/
    ├── CACHEDIR.TAG
    └── debug/

我们看到该项目下的所有package共享一个共同的 Cargo.lock 文件,该文件位于工作空间的根目录下。并且,所有包共享一个共同的输出目录,默认情况下是工作空间根目录下的一个名为target的目录,该target目录下的布局如下:

$tree -F -L 2 ./target
./target
├── CACHEDIR.TAG
└── debug/
    ├── build/
    ├── deps/
    ├── examples/
    ├── incremental/
    ├── libpackage2.d
    ├── libpackage2.rlib
    ├── libpackage3.d
    ├── libpackage3.rlib
    ├── package1*
    └── package1.d

我们在这下面可以找到所有package的编译输出结果,比如package1、libpackage2.rlib以及libpackage3.rlib。

当然,你也可以指定一个package来构建或运行:

$cargo build -p package1
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
$cargo build -p package2
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
$cargo run -p package1
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/package1`
Hello, world!

4.3.2.2 带有外部依赖和内部依赖的多package项目

我们复制一份my-workspace,改名为my-workspace-with-deps,修改一下package1/src/main.rs,为其增加外部依赖rand crate:

// organizing-rust-code/cargo/multi-packages/my-workspace-with-deps/package1/src/main.rs
extern crate rand;

use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    let num: u32 = rng.gen();
    println!("Random number: {}", num);
}

接下来,我们需要修改一下package1/Cargo.toml,手工加上对rand crate的依赖配置:

// organizing-rust-code/cargo/multi-packages/my-workspace-with-deps/package1/Cargo.toml
[package]
name = "package1"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.8.5"

保存后,我们执行package1的构建:

$cargo build -p package1
  Downloaded getrandom v0.2.14 (registry `rsproxy`)
  Downloaded libc v0.2.154 (registry `rsproxy`)
  Downloaded 2 crates (780.6 KB) in 1m 07s
   Compiling libc v0.2.154
   Compiling cfg-if v1.0.0
   Compiling ppv-lite86 v0.2.17
   Compiling getrandom v0.2.14
   Compiling rand_core v0.6.4
   Compiling rand_chacha v0.3.1
   Compiling rand v0.8.5
   Compiling package1 v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/organizing-rust-code/cargo/multi-packages/my-workspace-with-deps/package1)
    Finished dev [unoptimized + debuginfo] target(s) in 1m 46s

我们看到:cargo会自动下载package1的直接外部依赖以及相关间接依赖。构建成功后,可以执行一下package1的编译结果:

$cargo run -p package1
    Finished dev [unoptimized + debuginfo] target(s) in 0.09s
     Running `target/debug/package1`
Random number: 3840180495

接下来,我们再为package1添加内部依赖,比如依赖package2的编译结果:

// organizing-rust-code/cargo/multi-packages/my-workspace-with-deps/package1/src/main.rs

extern crate package2;
extern crate rand;

use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    let num: u32 = rng.gen();
    println!("Random number: {}", num);
    let result = package2::add(2, 2);
    println!("result: {}", result);
}

// organizing-rust-code/cargo/multi-packages/my-workspace-with-deps/package1/Cargo.toml
[package]
name = "package1"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
rand = "0.8.5"
package2 = { path = "../package2" }

我们看到:package1的main.rs依赖package2这个crate中的add函数,我们在package1的Cargo.toml中为package1添加了新依赖package2,由于package2仅仅存放在本地,所以这里我们使用了path方式指定package2的位置。

我们执行一下添加内部依赖后的package1:

$cargo run -p package1
    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/package1`
Random number: 2485645524
result: 4

4.4 小结

本文循序渐进地讨论了在Rust项目中如何组织代码的问题,这对于Rust初学者来说尤为有用。

我们首先回顾了Go语言中的代码组织方式,介绍了Go项目代码组织的两个层级:module和package。然后,我们将Rust项目可以分为两种类型:使用rustc编译器的项目和使用Cargo的项目。

对于rustc-only的项目,开发者需要编写自己的构建脚本来管理项目的构建过程。

文章从最简单的单文件rustc-only项目开始介绍,展示了如何使用rustc编译器来编译和运行这种项目,并逐步介绍了带有外部依赖的rustc-only项目以及多文件项目的情况,引出了rust module概念。

rustc-only项目很少用于生产环境,这种方式主要用于学习和了解Rustc编译器的功能机制以及Rust语言的代码组织抽象。

在实际开发中,使用Cargo来创建和管理Rust包是常见的做法。在本章的后半段,我们介绍了使用cargo管理的rust项目的代码组织情况,包括单package项目和多package项目以及如何为项目引入外部和内部依赖。

总体而言,本文旨在帮助初学者理解和掌握Rust项目的代码组织结构,以提高学习效率和学习效果。通过介绍rustc-only项目和cargo管理的项目,读者可以逐步了解Rust代码组织的基本概念和实践方法。

本文涉及的源码可以在这里下载。

4.5 参考资料


Gopher部落知识星球在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时,我们也会加强代码质量和最佳实践的分享,包括如何编写简洁、可读、可测试的Go代码。此外,我们还会加强星友之间的交流和互动。欢迎大家踊跃提问,分享心得,讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落,享受coding的快乐! 欢迎大家踊跃加入!

img{512x368}
img{512x368}

img{512x368}
img{512x368}

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻) – https://gopherdaily.tonybai.com

我的联系方式:

  • 微博(暂不可用):https://weibo.com/bigwhite20xx
  • 微博2:https://weibo.com/u/6484441286
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • Gopher Daily归档 – https://github.com/bigwhite/gopherdaily

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

Go早期的那些布道者

本文永久链接 – https://tonybai.com/2024/05/17/the-early-evangelists-of-go

Go自2009年开源至今已经快15年了!这期间,有很多人对Go语言的发展做出了重要贡献,起到了至关重要的作用,他们被视为Go语言的早期布道者和鼓吹者。他们向Go社区传达着Go的价值观、Go的最新发展、Go的使用方法以及最佳实践。

这些人有来自Go团队的,有来自Google但非Go团队的,也有Go的早期采纳者和贡献者。如今这些人的状况不尽相同。有些人依然在活跃在Go团队中,为Go的演进持续做着贡献;有些人由于各种原因,已经退居二线,但仍心系Go的发展;还有一些人则逐渐淡出Go社区,或者说不再复当年的那种热忱。

不过,无论哪种,这些人为Go语言的推广和发展都做出了不可磨灭的贡献,值得Gopher们铭记。在这篇文章中,我就来说说这些Go早期的布道师。也可以让后进入Go阵营的Gopher们了解一下过去的事情。

注:这里是不完全统计,还有很多早期Go布道师做出了重要贡献,限于篇幅,这里就不一一列举细数了。

Rob Pike

Rob Pike 是Go语言的共同创始人之一,他在2007年与Ken Thompson和Robert Griesemer一起开始了Go语言的开发。

作为一名经验丰富的计算机科学家,Rob Pike曾在贝尔实验室工作,参与了Plan 9和Inferno操作系统的开发,UTF-8也是他的杰作。此外,他还是C语言和UNIX操作系统的早期贡献者之一。

Rob Pike为Go语言的设计和实现做出了重大贡献。他的设计理念强调简洁、并发和高效,这些理念深深影响了Go语言的核心特性。在Go语言的早期发展阶段,Rob Pike几乎主导了Go语言规范的制定,并负责了许多关键语言特性的开发。在Ken Thompson退休后,他成为了Go语言第一代的领军人物。

除了技术贡献,Rob Pike还是最为积极的Go语言推广者。他在Google内外的各种会议和技术活动中发表演讲,介绍Go语言的优势、应用场景以及使用方法。他的演讲风格生动有趣,深受开发者的喜爱。此外,Rob Pike还撰写了大量关于Go语言的博客和技术文档,为社区提供了宝贵的学习资源。


截图来自golang.design

他的“3 Day Go Course”也是最早、最权威的Go教程,即便在今天看来略有些Outdated了:)。

注:关于Rob Pike的早期3-days Go Course ppt,可以在这里下载https://www.cs.cmu.edu/afs/cs.cmu.edu/academic/class/15440-f11/go/doc/

现如今,Rob Pike已经从Google退休了,并长居澳大利亚,并继续为Go语言的发展做着贡献。尽管他不再像早期那样频繁地参与社区活动,但他的影响力依然深远,Go 1.18泛型发布前,Rob Pike就及时纠正了Go团队对泛型的支持策略

Rob Pike的工作为Go语言奠定了坚实的基础,使其成为现代编程语言中的一颗璀璨明珠。

Robert Griesemer

Robert Griesemer是Go语言的另一位共同创始人。他在加入Go团队之前,他曾参与Google V8 JavaScript引擎、Sawzall语言、Java HotSpot虚拟机和Strongtalk系统的工作,拥有丰富的编程语言设计和实现经验。

Griesemer在设计和实现Go语言方面发挥了关键作用,尤其是在语法和编译器的开发上。Griesemer的设计理念强调语言的简洁性和易用性,这使得Go语言在开发者中迅速获得了广泛的认可。他致力于优化编译器性能,使Go程序能够高效地运行在各种平台上。Griesemer还参与了Go语言标准库的设计和实现,为开发者提供了丰富的工具和资源。

在Go语言的推广方面,Griesemer同样不遗余力。他经常参与技术会议和社区活动,分享Go语言的设计理念和最佳实践,他也是唯一在GopherChina上现场进行分享的Go语言之父。

他的技术讲座深入浅出,帮助许多开发者快速上手Go语言。此外,Griesemer还与其他团队成员合作撰写了多篇技术论文和博客,进一步推动了Go语言的普及。


截图来自golang.design

目前,Griesemer依然在Google Go团队工作,奋战在Go语言的开发和优化的第一线。他和Ian Lance Taylor共同设计和实现了Go泛型机制,大幅提升了Go的表达能力。他的工作对Go语言的成功起到了至关重要的作用,使其成为许多大型项目和企业的首选开发语言。

Brad Fitzpatrick

Brad Fitzpatrick是一位资深的美国程序员。在加入Go团队之前,Fitzpatrick就因创建LiveJournal和Memcached而闻名。后来加入Google,成为Go团队的重要成员,并在Go语言社区中拥有很高的声誉。

在Go语言的发展过程中,Fitzpatrick为许多关键组件做出了贡献,尤其是在网络库和并发编程模型方面。他创建了诸如HTTP包和context包等核心库,这些库广泛应用于Go语言的网络编程中。

Fitzpatrick不仅在技术上对Go做出了杰出贡献,他还是Go社区活动的积极参与者,是Go团队中参与社区活动的“典范”。他经常在技术会议和用户组活动中发表演讲,分享自己的经验和最佳实践。Fitzpatrick的工作帮助许多开发者更好地理解和使用Go语言,推动了社区的发展。


截图来自golang.design

几年前,Fitzpatrick离开了Google并重新创业,他联创的Tailscale基于WireGuard和Go打造号称世界上最容易使用的安全private network产品。一些Go commiter和Ex-googler也被Fitzpatrick招入tailscale。Tailscale团队后续也成为了Go的重要贡献团队,比如go4org下的很多实用包,像internunsafe-assume-no-moving-gcmem等。其中的intern还是Go 1.23中加入的unique包的灵感之源。

仍然活跃在Go开源社区的Fitzpatrick依旧继续为Go语言和其他开源项目做着贡献,他的热情和奉献精神使他成为Go社区中备受尊敬的领袖之一。

Andrew Gerrand

Andrew Gerrand是Go团队的早期成员之一,他为Go团队工作七年,早期也是Go项目的Top10贡献者。但他在Go团队的主要职责其实是围绕该语言构建社区并管理开源项目。Gerrand的工作帮助许多开发者快速上手Go语言。他编写的Go语言文章深入浅出,覆盖了从基础语法到高级特性的方方面面。此外,Gerrand还创建了Go Playground,一个在线编程环境,使开发者能够方便地编写和运行Go代码。

除了技术文档,Gerrand还积极参与社区活动。他组织和主持了多次Go语言会议(比如GopherCon)和用户组活动,推动了Go社区的建设和发展,是Rob Pike做Go社区推广的好搭档。。Gerrand还经常在Go语言的官方博客上发表文章,介绍Go的最新特性和最佳实践,官博早期的大部分文章都出自他手。由此看来,Gerrand在早期对Go语言的推广和社区建设做出过重要贡献。

从2016年开始,他跟随Rob Pike转战Upspin项目,这个项目活跃了一年多,虽然现在依然在更新,但关注度目前已不是很高。Gerrand目前已经远离Go项目开发,并且很少撰文或参与Go社区活动。但他仍然在upspin、deps.dev等google项目上使用Go进行着开发和维护工作。

如果要关注Gerrand的日常,可以在X上follow他的账号

Russ Cox

Russ Cox是早期Go语言团队的重要成员之一,对Go语言的设计和实现做出了重大贡献。他拥有麻省理工学院的计算机科学博士学位,曾在贝尔实验室和Rob Pike一起在Plan9项目上工作过。Cox在加入Google后,成为Go语言项目的核心开发者之一。

Russ Cox对Go的贡献是全方位的,无论在语言特性、工具链、社区推广等方面都有很大建树。这也使得他在Rob Pike退休后,迅速成为了Go语言的第二代领军人物。

近几年进入Go阵营的开发者对Russ Cox不可谓不熟悉,他主导了vendor、type alias、Go module泛型等设计和实现,直接引领了Go的演进方向。

近几年,Russ Cox经常在GopherCon大会上代表Go团队发表主旨演讲,并在官博亲自撰文,向Go社区传达Go语言的演进思路与方向。经过多年历练,Russ Cox逐渐扛起了Go这杆大旗,接过了Rob Pike手中沉甸甸的Go接力棒。

Dmitry Vyukov

Dmitry Vyukov是一位俄罗斯大神级程序员,英特尔并行编程黑带级程序员。加入Google后一直从事性能优化方面的工作,包括并发无锁算法、执行跟踪和竞争检测工具、fuzzing工具等。Vyukov虽然不是Go团队成员,但他对Go的贡献却是核心级的,主要包括:

除了技术贡献,Vyukov早期也会参与一些会议和社区活动,虽然不多,主要是推广Go execution tracer和go-fuzz工具

目前,Vyukov依然在Google工作,也在继续为Go语言的发展做着力所能及的贡献。

Steve Francia

Steve Francia是早起Go语言社区的重要成员之一,对Go语言的推广和社区建设做出了重要贡献。Francia在加入Google之前,曾在MongoDB、Docker公司工作,拥有丰富的开发和管理经验。之后他加入Google,在Go语言项目中担任开发者关系经理,负责推动Go语言在企业中的应用。

Francia最为人称道的是他开发并开源的几个Go项目,包括goHugoCobraviper等。其中的hugo,一个基于Go语言的静态网站生成器,广受开发者的欢迎。

除了技术贡献,Francia还致力于社区建设。他组织和主持了多次Go语言会议和用户组活动,推动了Go社区的发展。Francia还在其个人博客上撰写了大量关于Go语言的技术文档,为开发者提供了宝贵的学习资源。

目前,Francia已经离开了Google和Go团队,并在一家位于纽约的初创公司担任CTO。目前在Go社区,他已不再像以前那样活跃,但他的几个开源项目依然保持积极开发中,也有人协助他打理这些开源项目。

总之,Francia的工作对Go语言的普及和社区建设起到了重要的作用,帮助Go成为开发者们最喜爱的编程语言之一。

Jaana Dogan

Jaana Dogan是这个布道者列表中唯一的女性程序员。她曾是Go语言团队的一名工程师,对Go语言的性能优化、诊断和工具开发做出了重要贡献。但她在Go团队工作的时间并不长,很快就离开了Go团队,原因未知。目前她供职在github。

Dogan对社区的贡献主要体现在其关于Go的独特观点的博客文章诊断相关的技术文档以及其开源的诸多项目,比如:heygopsgovanityurlsstatik等。这些项目都不大,但却十分实用。

很多gopher中使用hey进行http压测,gops则是高频使用的Go辅助诊断工具,govanityurls则是我的《小厂内部私有Go module拉取方案》的重要组件。statik也是Go 1.16版本引入go:embed之前在可执行文件中嵌入静态文件的一个可选工具。

Dogan在社区中以其深入的技术见解和乐于分享的态度,赢得了广泛的尊重和赞誉。不过,离开Google后,尤其是进入github后,Dogan在Go上面的投入似乎变少了很多,博客文章基本也不更新了,也没有新的开源项目产出,这对Go社区来说算是一个“损失”吧。

Brian KetelsenErik St. Martin

将Brian Ketelsen和Erik St. Martin放在一起说,是因为他们一起对Go语言以及Go社区的最大贡献就是共同创办了GopherCon,这是全球最大也是最权威的Go语言开发者大会,每年都会吸引大量来自世界各地的Go开发者。GopherCon不仅是一个技术交流的平台,也是Go社区的重要聚会,促进了开发者之间的交流与合作。两人在组织和推动GopherCon的过程中,展示了他们对Go语言的热情和奉献精神。今年(2024年)也是GopherCon诞生的第10个年头,想必这又是一场Go语言和Go社区的盛会!

除了会议组织和社区活动,Ketelsen和St. Martin还与William Kennedy联合撰写了关于Go语言的技术书籍《Go in Action》。这本书深入浅出地介绍了Go语言的基础知识和实际应用,为开发者提供了系统的学习资源。此外,他们还参与了多个开源项目,为Go语言的生态系统做出了重要贡献。

目前,Ketelsen和St. Martin都供职于微软公有云团队,仍然活跃在Go社区。

Francesc Campoy

Francesc Campoy是Go语言社区的知名讲师和布道者,对Go语言的推广和普及做出了重要贡献。Campoy在加入Google之前,曾在西班牙的一家软件公司工作,拥有丰富的开发经验。他在Go语言项目中担任开发者关系经理,负责向开发者推广Go语言。Campoy也曾作为GopherChina的嘉宾在多年前来到中国布道!

Campoy的工作帮助许多开发者快速上手Go语言。他制作了一系列高质量的Go语言视频教程“Just for func”,涵盖了从基础语法到高级特性的方方面面。这些视频教程深入浅出,受到了广泛的欢迎。此外,Campoy还创建了Go语言的官方YouTube频道,定期发布技术讲座和演示,进一步推动了Go语言的普及。

2016年,Campoy从Google离职,加入Dgraph Labs,负责原生GraphQL数据库dgraph以及键值数据库badger的开发。后来Dgraph labs内讧,Campoy转投到apple名下。

进入Apple后,Campoy就渐渐从Go社区淡出了。但他仍然会进行一些Go项目的开发,Github的activity中有他的一些活动记录,但更多地是对私有仓库的贡献。

Dave Cheney

如果说Go团队之外,谁是大家最喜欢的Go布道师和意见领袖,矮胖子Dave Cheney肯定可以拿到数一数二的选票。

相信早期学过Go语言的Gopher们,没有没读过Dave Cheney的个人博客的。他的博客从2010开始写的内容就几乎都与Go相关,并且思维缜密,写作风格深入浅出,颇受Gopher欢迎。

很多人还参与过他在世界各地举办的Go用户活动。Dave Cheney也是来到中国GopherChina大会最多的Go布道师,为中国Gopher带来了精彩的演讲以及极具干货的大会前Workshop

除了技术资料外,Dave Cheney早期在Go项目的issue上、在go-nuts邮件列表以及stackoverflow上也是非常活跃,非常乐于帮助那些想给Go项目做出贡献的gopher融入。同时他在github.com/pkg下开源的诸多项目也非常实用(比如https://github.com/pkg/errors),深受大家欢迎。

更多关于Dave Cheney对Go语言的贡献,可以阅读其个人博客的about页面

不过不知何故,从2021年初开始,Dave Cheney的博客开始停更,他在社区的声音也逐渐消失。直到今年年初,Dave Cheney才又更新了一篇名为“Microblog: TestMain can cause one to question reality”的文章。

不过从Dave Cheney的github Contribution activity来看,Dave仍然在大量的编写代码,只是这些代码是commit到private仓库的。

希望Dave Cheney能早日回归Go社区,并恢复当初的那份热忱。

Bill Kennedy

Bill Kennedy是Go语言社区的知名讲师和布道者,对Go语言的推广和普及做出了重要贡献,并且这种贡献一直持续至今。

Kennedy是ardan labs的CTO,也是该公司的主要讲师,他最拿手的Ultimate Go培训已经开办了十多年了,每年都会在全球进行很多场培训,此外,GopherCon大会前的Workshop总是少不了Bill Kennedy的training。

除了Training,Ardan Labs早期的博客文章也堪称精品,不少Gopher因这些文章而受益!Kennedy的工作主要是帮助许多开发者快速上手Go语言。他还撰写了多本关于Go语言的技术书籍,如《Go in Action》和《Ultimate Go》。这些书籍深入浅出,涵盖了从基础语法到高级特性的方方面面,为开发者提供了系统的学习资源。

除了技术培训,Kennedy还积极参与Go语言演进和Go社区活动之中,这其中一个最典型的事件就是2019年他代表广大Gopher用户发给Go团队的公开信,极力组织不成熟的try关键字提案进入Go语言。

目前,Kennedy依然活跃在Go社区,继续推动Go语言的发展和普及。

Mat Ryer

Mat Ryer是Go语言社区的一位资深开发者和布道者,他是一名英国程序员。他之前在Go社区非常活跃,积极参与技术会议和社区活动,分享Go语言在实际项目中的应用经验和最佳实践,其中最持久的莫过于主持Go Time播客了!”Go Time”是一个专注于Go编程语言的播客节目。该播客由Changelog Media出品,主要内容涵盖了Go语言的新特性、最佳实践、社区新闻、工具和库的推荐,以及与Go生态系统相关的各种话题。节目通常会邀请Go社区的知名开发者、贡献者和专家作为嘉宾,分享他们的经验和见解。截至写本文时,该播客已经发布了315期,每期都有音频和文字稿

除了上述活动,Mat Ryer还是《Go Programming Blueprints》一书的作者。

如今Mat Ryer依然活跃在Go社区,他供职于Grafana,依然从事着Go语言开源项目的开发,同时主持Go Time播客以及组织和参与各种Go用户会议。

参考资料


Gopher部落知识星球在2024年将继续致力于打造一个高品质的Go语言学习和交流平台。我们将继续提供优质的Go技术文章首发和阅读体验。同时,我们也会加强代码质量和最佳实践的分享,包括如何编写简洁、可读、可测试的Go代码。此外,我们还会加强星友之间的交流和互动。欢迎大家踊跃提问,分享心得,讨论技术。我会在第一时间进行解答和交流。我衷心希望Gopher部落可以成为大家学习、进步、交流的港湾。让我相聚在Gopher部落,享受coding的快乐! 欢迎大家踊跃加入!

img{512x368}
img{512x368}

img{512x368}
img{512x368}

著名云主机服务厂商DigitalOcean发布最新的主机计划,入门级Droplet配置升级为:1 core CPU、1G内存、25G高速SSD,价格5$/月。有使用DigitalOcean需求的朋友,可以打开这个链接地址:https://m.do.co/c/bff6eed92687 开启你的DO主机之路。

Gopher Daily(Gopher每日新闻) – https://gopherdaily.tonybai.com

我的联系方式:

  • 微博(暂不可用):https://weibo.com/bigwhite20xx
  • 微博2:https://weibo.com/u/6484441286
  • 博客:tonybai.com
  • github: https://github.com/bigwhite
  • Gopher Daily归档 – https://github.com/bigwhite/gopherdaily

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats