自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Gopher的Rust第一課:第一個Rust程序

開發(fā) 前端
本文介紹了如何使用Rust編寫"Hello, World"程序,并分別給出了rustc版和cargo版的hello, world程序版本。

經(jīng)過上一章[1]的學(xué)習(xí),我想現(xiàn)在你已經(jīng)成功安裝好一個Rust開發(fā)環(huán)境了,是時候擼起袖子開始寫Rust代碼了!

程序員這個歷史并不算悠久的行當(dāng),卻有著一個歷史悠久的傳統(tǒng),那就是每種編程語言都將一個名為“hello, world”的示例作為這門語言學(xué)習(xí)的第一個例子,這個傳統(tǒng)始于20世紀(jì)70年代那本大名鼎鼎的由布萊恩·科尼根(Brian W. Kernighan)與C語言之父丹尼斯·里奇(Dennis M. Ritchie)合著的《C程序設(shè)計語言》。

圖片圖片

在這一章中,我們也將遵從傳統(tǒng),從編寫和運行一個可以打印出“hello, world”的Rust示例程序開始我們正式的Rust編碼之旅。我希望通過這個示例程序你能夠?qū)ust程序結(jié)構(gòu)有一個直觀且清晰的認識。

3.1 Hello, World

“Hello, World”是一門編程語言的最簡單示例的表達形式。在Go中,我們可以像下面這樣編寫Go版本的Hello, World程序:

package main

func main() {
    println("Hello, World!")
}

為了簡單,我們甚至沒有使用fmt包的Printf系列函數(shù)(這樣就可以減少一行導(dǎo)入包的語句),而是用了內(nèi)置函數(shù)println來完成將“Hello, World”輸出到控制臺(更準(zhǔn)確的說是標(biāo)準(zhǔn)錯誤(stderr))的任務(wù)。

Rust版本的Hello, World可以比Go還要簡潔,我們在一個目錄下(比如rust-guide-for-gopher/helloworld/rustc)創(chuàng)建一個hello_world.rs的文件。哦,沒錯!rust的源碼文件都是以.rs作為源文件擴展名的。并且對于多個單詞構(gòu)成的文件名,rust的慣例是采用全小寫單詞+下劃線連接的方式命名。這個hello_world.rs文件的內(nèi)容如下:

fn main() {
    println!("Hello, World!");
}

相比于Go在每個源文件中都要使用package指定該文件歸屬的包名,Rust無需這樣的一行。和Go一樣,這里的main是函數(shù),所有可執(zhí)行的Rust程序都必須有一個main函數(shù),它是Rust程序的入口函數(shù)。和Go使用func函數(shù)聲明函數(shù)不同,Rust聲明函數(shù)的關(guān)鍵字為fn。在這個main函數(shù)中,我們調(diào)用println!將“Hello, World!”輸出到控制臺上。

不過,和Go內(nèi)置的println函數(shù)不同的是,這里的println!并非是一個函數(shù),而是一個**Rust宏(macro)**。

如果你只是學(xué)過Go,而沒有學(xué)過C/C++語言,你甚至都不會知道宏(macro)是什么。在Rust中,宏是一種用于代碼生成和轉(zhuǎn)換的元編程工具。宏允許你在編譯時根據(jù)一定的模式或規(guī)則來擴展代碼。Rust宏分為聲明宏(Declarative Macros)和過程宏(Procedural Macros)。println!就屬于聲明宏,它由macro_rules! 宏定義,我們在Rust標(biāo)準(zhǔn)庫的源碼中可以看到其定義:

// $(rustc --print sysroot)/lib/rustlib/src/rust/library/std/src/macros.rs

#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(test), rustc_diagnostic_item = "println_macro")]
#[allow_internal_unstable(print_internals, format_args_nl)]
macro_rules! println {
    () => {
        $crate::print!("\n")
    };
    ($($arg:tt)*) => {{
        $crate::io::_print($crate::format_args_nl!($($arg)*));
    }};
}

在Rust源碼編譯過程中,聲明宏是在最開始的預(yù)處理階段進行擴展的,我們也可以通過nightly版的rustc命令來查看println!宏展開后的結(jié)果(-Z選項只能在nightly版本中使用):

$rustc +nightly-2022-07-14-x86_64-apple-darwin  -Zunpretty=expanded  hello_world.rs
#![feature(prelude_import)]
#![no_std]
#[prelude_import]
use ::std::prelude::rust_2015::*;
#[macro_use]
extern crate std;
fn main() {
    {
        ::std::io::_print(::core::fmt::Arguments::new_v1(&["Hello, World!\n"],
                &[]));
    };
}

我們看到:println!宏被替換為一個標(biāo)準(zhǔn)庫下的函數(shù)(_print)的調(diào)用。btw,到這里,你可能和我一樣,看不懂println!展開后的代碼,沒關(guān)系,我們后續(xù)會逐步學(xué)習(xí)并掌握這些語法的。此外,宏是Rust的高級特性,這里也不展開說了。

另外一個和Go在語法上有所不同的是,Rust在每行語句后面都要顯式使用分號,對于Gopher而言,這個很容易遺忘。

接下來,我們來編譯和運行一下這個Rust版的Hello,World!,編譯運行Rust代碼的最簡單方法就是通過rustc編譯器將rust源碼文件編譯為可執(zhí)行程序:

$rustc hello_world.rs

$ls
hello_world*  hello_world.rs

我們看到,示例通過調(diào)用rustc將hello_world.rs編譯為了hello_world可執(zhí)行文件。

運行rustc編譯后的可執(zhí)行文件將得到下面輸出結(jié)果:

$./hello_world
Hello, World!

我們看到"Hello, World!"被打印到控制臺。

如果覺得默認編譯出的hello_world文件名字較長,我們也可以像go build -o那樣指定rustc編譯后得到的目標(biāo)可執(zhí)行文件的名字,下面的命令通過-o選項將編譯后的程序命名為hello:

$rustc -o hello hello_world.rs

rustc編譯出來的二進制文件size并不大,僅有400多KB(而Go默認構(gòu)建的Hello, World!有1.3MB,在我的macOS上):

$ls -lh
total 856
-rwxr-xr-x  1 tonybai  staff   423K  4 20 17:56 hello_world*

我們還可以通過去掉symbols的方式繼續(xù)讓其“瘦身”到不到300KB(通過go build -ldflags="-s -w" helloworld.go去除符號表和調(diào)試信息的Go二進制程序還有近900K的大小):

$rustc -C strip=symbols hello_world.rs 
$ll -h
total 608
-rwxr-xr-x  1 tonybai  staff   297K  4 20 17:57 hello_world*

上面的"Hello, World"程序雖然足夠簡單,也能夠運行,但對于初學(xué)者而言,它有兩個“不足”:一來這個例子的確“太簡單”,簡單到無法充分展示單個Rust源碼文件的結(jié)構(gòu);二來這個示例只使用了一個單個源文件,與實際開發(fā)中那種由多個文件組成的Rust實用工程有差別,同樣無法幫助我們理解實用性的Rust工程的結(jié)構(gòu)。

為了更好地理解Rust工程與單個源文件的構(gòu)成,我們將編寫一個稍微復(fù)雜一點的版本,它將使用Rust的構(gòu)建管理工具cargo建立,并使用Rust標(biāo)準(zhǔn)庫中的std::io模塊進行輸入/輸出操作。

3.2 cargo版本的Hello, World

在實際開發(fā)中,Rust程序通常由多個源文件組成,并使用Cargo作為構(gòu)建系統(tǒng)和包管理器。Cargo可以幫助我們管理項目的源代碼、依賴庫、構(gòu)建任務(wù)等。下面我們就來創(chuàng)建一個使用Cargo的"Hello, World"。

3.2.1 使用Cargo創(chuàng)建Hello,World

我們在一個目錄下(比如:rust-guide-for-gopher/helloworld/cargo)執(zhí)行下面命令來創(chuàng)建hello_world:

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

cargo默認創(chuàng)建了一個binary(application)類型的rust package,我們來看看初始情況下這個rust package下都有哪些內(nèi)容:

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

1 directory, 2 files

其中,Cargo.toml是Rust包的清單(manifest)文件。它包含有關(guān)包及其依賴項的元數(shù)據(jù)。以下是上面Cargo.toml文件的全部內(nèi)容:

// 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]

其中package下面的字段含義如下:

  • name: 包的名稱;
  • version: 包的版本,遵循語義化版本控制規(guī)則;
  • edition: 包使用的Rust版本(edition)。在這里,它被設(shè)置為目前的最新edition:2021版。edition提供了一種向后兼容的方式來演化和改進Rust。每個edition都是向后兼容的,這意味著舊edition下編寫的Rust代碼可以繼續(xù)在新edition版本的Rust下編譯和運行,而無需進行修改。這樣,開發(fā)者可以按照自己的節(jié)奏選擇是否遷移到新的edition。

dependencies下面則是會記錄該package對第三方依賴的情況,這個示例中并無三方依賴,因此這里為空。

我們的代碼放在了src目錄下,這也是rust包的標(biāo)準(zhǔn)布局。為了更好地理解Rust程序的構(gòu)成,我們將編寫一個稍微復(fù)雜一點的Hello, World!版本,它使用Rust標(biāo)準(zhǔn)庫中的std::io模塊進行輸入/輸出操作:

// rust-guide-for-gopher/helloworld/cargo/hello_world/src/main.rs
use std::io;
use std::io::Write;

fn main() {
    let mut output = io::stdout();
    output.write(b"Hello, World!").unwrap();
    output.flush().unwrap();
}

這個Rust的"Hello, World"程序展示了一個典型的Rust源文件結(jié)構(gòu),包括導(dǎo)入語句、主函數(shù)定義以及一系列的方法調(diào)用。它演示了如何使用標(biāo)準(zhǔn)庫的io模塊來向標(biāo)準(zhǔn)輸出流打印"Hello, World!"。下面是對其程序結(jié)構(gòu)的簡單總結(jié):

  • 導(dǎo)入語句

源文件在最開始處使用use std::io; 和use std::io::Write;這兩行導(dǎo)入了標(biāo)準(zhǔn)庫中的io模塊及其Write trait。這樣程序就可以在后面的代碼中直接使用io和Write,而無需完整地寫出它們的命名空間。這里我們先不用關(guān)心trait是什么,你大可將其理解為和Go interface差不多的語法元素就行了。

  • 主函數(shù)

main定義了程序的入口點。Rust 程序從main函數(shù)開始執(zhí)行。

  • 可變變量

let mut output = io::stdout(); 這行代碼創(chuàng)建了一個可變變量output,它綁定到了一個標(biāo)準(zhǔn)輸出流(stdout)。mut關(guān)鍵字表示該變量是可變的,可以在后續(xù)代碼中修改它的值。關(guān)于變量以及綁定,我們在后面有專門的章節(jié)說明。這里要注意的是,和Go變量不同的是,Rust中的變量默認是不可變的,只有顯式用mut聲明的變量才是可變的。

  • 方法調(diào)用

output.write(b"Hello, World!").unwrap(); 調(diào)用了output的write方法,傳遞了一個字節(jié)串作為參數(shù)。該方法用于將字節(jié)寫入輸出流。unwrap方法用于處理方法調(diào)用可能產(chǎn)生的錯誤,它在這里表示“我相信這個方法調(diào)用會成功,如果不成功,就讓程序 panic”。同理,output.flush().unwrap()也是這樣的。關(guān)于錯誤以及異常處理的話題,我們會在后面進行專題性學(xué)習(xí)。

理解了源碼后,我們來編譯和運行一下這個程序,這次我們不再使用rustc,而是用cargo來實現(xiàn)。

3.2.2 使用Cargo構(gòu)建Hello, World

要構(gòu)建上面的示例程序,我們只需在項目根目錄下運行下面命令:

$cargo build
   Compiling hello_world v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/helloworld/cargo/hello_world)
    Finished dev [unoptimized + debuginfo] target(s) in 1.23s

構(gòu)建成功后,我們再來查看一下當(dāng)前項目下的結(jié)構(gòu)變化:

$tree -F
.
├── Cargo.lock
├── Cargo.toml
├── src/
│   └── main.rs
└── target/
    ├── CACHEDIR.TAG
    └── debug/
        ├── build/
        ├── deps/
        │   ├── hello_world-07284f5d84374479*
        │   ├── hello_world-07284f5d84374479.1atc14vk0u28taij.rcgu.o
        │   ├── hello_world-07284f5d84374479.1bu89c2i9mazzqif.rcgu.o
        │   ├── hello_world-07284f5d84374479.26e3nxhmk9lhy9zy.rcgu.o
        │   ├── hello_world-07284f5d84374479.29l81xyv0i4g8s88.rcgu.o
        │   ├── hello_world-07284f5d84374479.41i7ln85cwseljfw.rcgu.o
        │   ├── hello_world-07284f5d84374479.4iz3ubiqrvegnjdp.rcgu.o
        │   ├── hello_world-07284f5d84374479.53vu8cjirf8g6rnw.rcgu.o
        │   ├── hello_world-07284f5d84374479.5f6ye0ayl23rccqv.rcgu.o
        │   └── hello_world-07284f5d84374479.d
        ├── examples/
        ├── hello_world*
        ├── hello_world.d
        └── incremental/
            └── hello_world-16yuztatbr0vh/
                ├── s-gvfwmugno5-1gy801r-1i2g78r4nmg489ix0nuktmqgb/
                │   ├── 1atc14vk0u28taij.o
                │   ├── 1bu89c2i9mazzqif.o
                │   ├── 26e3nxhmk9lhy9zy.o
                │   ├── 29l81xyv0i4g8s88.o
                │   ├── 41i7ln85cwseljfw.o
                │   ├── 4iz3ubiqrvegnjdp.o
                │   ├── 53vu8cjirf8g6rnw.o
                │   ├── 5f6ye0ayl23rccqv.o
                │   ├── dep-graph.bin
                │   ├── query-cache.bin
                │   └── work-products.bin
                └── s-gvfwmugno5-1gy801r.lock*

9 directories, 28 files

我們看到cargo build執(zhí)行后,項目下多出了好多目錄和文件。這些目錄和文件都是做什么的呢?我們挑選主要的來看一下。

  • Cargo.lock文件

Cargo的鎖定文件,用于記錄每個依賴項的確切版本號,以保證構(gòu)建的可重復(fù)性。

這個示例中由于沒有使用第三方依賴,這個Cargo.lock文件中的內(nèi)容不具典型性:

# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

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

另外Cargo.lock文件完全由cargo自動管理,開發(fā)人員不需要也不應(yīng)該對其進行手動修改。

  • target目錄

存放構(gòu)建輸出的目錄,用于存儲編譯后的目標(biāo)文件和可執(zhí)行文件。

  • target/CACHEDIR.TAG

用于標(biāo)記target目錄為一個緩存目錄的文件。它的內(nèi)容如下:

$cat CACHEDIR.TAG 
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by cargo.
# For information about cache directory tags see https://bford.info/cachedir/

這是一個符合Cache Directory Tagging Specification[2]的Tag文件。

  • target/debug

調(diào)試模式下的構(gòu)建輸出目錄,存儲生成的可執(zhí)行文件和相關(guān)文件。

  • target/debug/incremental

增量編譯的目錄,用于存儲增量編譯過程中的臨時文件和緩存。

Rust編譯過程緩慢,這個對比Go簡直就是地下天上。在日常開發(fā)中,基于增量編譯的文件進行增量構(gòu)建可以大幅縮短編譯時間。

  • target/debug/build

編譯過程中生成的臨時構(gòu)建文件的目錄。

  • target/debug/deps

存儲編譯生成的目標(biāo)文件(.o 文件)和相關(guān)的依賴項。

  • target/debug/hello_world

調(diào)試模式下生成的可執(zhí)行文件。

  • target/debug/hello_world.d

與hello_world相關(guān)的依賴關(guān)系信息的文件。

執(zhí)行debug目錄下的hello_world將得到如下輸出:

$./target/debug/hello_world 
Hello, World!

在Go中我們可以使用go run來直接編譯和運行Go源碼文件,cargo也提供了該功能,我們在項目根目錄下運行cargo run也可以編譯和執(zhí)行hello_world:

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

無論是cargo run還是cargo build,默認構(gòu)建的都是debug版本的可執(zhí)行程序,程序中包含大量符號信息和調(diào)試信息,并且其優(yōu)化級別也不是很高。發(fā)布到生產(chǎn)環(huán)境的程序應(yīng)該是release模式下的,通過--release參數(shù),我們可以構(gòu)建release版本的可執(zhí)行程序:

$cargo build --release
   Compiling hello_world v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/helloworld/cargo/hello_world)
    Finished release [optimized] target(s) in 1.06s

構(gòu)建后,target目錄下會多出一個release目錄,其下面的內(nèi)容如下:

$tree -F target/release 
target/release
├── build/
├── deps/
│   ├── hello_world-c41defdc625f9244*
│   └── hello_world-c41defdc625f9244.d
├── examples/
├── hello_world*
├── hello_world.d
└── incremental/

4 directories, 4 files

相對于debug版本,release版本由于實時了大量優(yōu)化,通常其構(gòu)建時間會比debug版本要長。但構(gòu)建出的release版本的size則要小很多。

無論是debug,還是release版,target下面都生成了許多中間文件,如果要清理文件并重頭構(gòu)建,我們可以使用cargo clean命令將target徹底清除:

$cargo clean
     Removed 40 files, 2.1MiB total

當(dāng)然cargo clean也支持一些命令行參數(shù),可以選擇清除哪些文件。

3.2.3 使用Cargo創(chuàng)建library類包

通過上面的例子,我們知道cargo new默認創(chuàng)建的binary類型的rust package,如果我們要創(chuàng)建library類型的rust package,我們需要向cargo new傳遞--lib選項。下面的命令創(chuàng)建一個名為foo的library類型的rust package:

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

我們看一下foo package下的目錄結(jié)構(gòu):

$tree -F foo
foo
├── Cargo.toml
└── src/
    └── lib.rs

1 directory, 2 files

和binary類不同的是,src目錄下不再是main.rs,而是lib.rs,它是library類package的入口:

//rust-guide-for-gopher/helloworld/cargo/foo/lib.rs

pub fn add(left: usize, right: usize) -> usize {
    left + right
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }
}

lib.rs中只是一個library類package的入口模板,開發(fā)人員需要根據(jù)自己的需要對其進行調(diào)整。關(guān)于lib.rs中的內(nèi)容,我們將在下一章講解Rust代碼組織時做細致說明,這里就不展開說了。

對于library類Rust package,我們同樣可以通過cargo build和cargo build --release構(gòu)建,下面是執(zhí)行構(gòu)建后目錄文件情況:

$tree
.
├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── target
    ├── CACHEDIR.TAG
    ├── debug
    │   ├── build
    │   ├── deps
    │   │   ├── foo-24c6d6228c521501.2k5t0f94hnorqpgh.rcgu.o
    │   │   ├── foo-24c6d6228c521501.d
    │   │   ├── libfoo-24c6d6228c521501.rlib
    │   │   └── libfoo-24c6d6228c521501.rmeta
    │   ├── examples
    │   ├── incremental
    │   │   └── foo-m2biu8poxl6i
    │   │       ├── s-gvg68shtlp-1oqrf4n-irxhgoe7rhwmtvj6jwexcu0h
    │   │       │   ├── 2k5t0f94hnorqpgh.o
    │   │       │   ├── dep-graph.bin
    │   │       │   ├── query-cache.bin
    │   │       │   └── work-products.bin
    │   │       └── s-gvg68shtlp-1oqrf4n.lock
    │   ├── libfoo.d
    │   └── libfoo.rlib
    └── release
        ├── build
        ├── deps
        │   ├── foo-9f2dd76beda509bd.d
        │   ├── libfoo-9f2dd76beda509bd.rlib
        │   └── libfoo-9f2dd76beda509bd.rmeta
        ├── examples
        ├── incremental
        ├── libfoo.d
        └── libfoo.rlib

14 directories, 20 files

我們看到,無論是debug還是release,cargo build構(gòu)建的結(jié)果都是libfoo.rlib。.rlib文件是Rust的靜態(tài)庫文件,通常用于代碼的模塊化和重用,我們在后續(xù)章節(jié)講解中,會詳細說明如何使用這些構(gòu)建出來的靜態(tài)庫。

3.3 小結(jié)

本文介紹了如何使用Rust編寫"Hello, World"程序,并分別給出了rustc版和cargo版的hello, world程序版本。

在這個過程中,文章還介紹了Rust中的宏概念,并展示了如何使用println!宏來輸出文本。

之后,文章聚焦于使用Cargo構(gòu)建的hello,world程序版本,介紹了cargo的構(gòu)建、清理、debug和release版本的區(qū)別等,最后還提及了如何使用cargo創(chuàng)建library類的Rust package。

cargo貫穿Rust程序的整個生命周期,在后續(xù)的每一章中可能都會提及cargo。

責(zé)任編輯:武曉燕 來源: 白明的贊賞賬戶
相關(guān)推薦

2024-06-27 11:08:45

2024-04-22 08:06:34

Rust語言

2024-06-17 09:00:08

2013-10-30 22:10:28

Clouda程序

2021-02-16 11:04:26

RustGo華為

2009-06-26 16:07:43

MyEclipse開發(fā)Hibernate程序

2012-05-25 15:20:38

XNA

2023-05-19 08:49:58

SQLAlchemy數(shù)據(jù)庫

2023-07-11 13:34:19

Rust開發(fā)軟件

2024-03-13 13:53:10

C++程序開發(fā)

2010-03-25 16:04:56

Python程序執(zhí)行

2025-02-27 00:00:15

2019-12-31 08:00:00

DebianLinuxApple Swift

2011-06-08 10:01:36

Windows Pho 應(yīng)用程序

2022-11-01 07:23:55

Dockernetcore程序

2011-06-08 10:24:38

Windows Pho 應(yīng)用程序

2011-06-24 13:38:32

QT 編譯 安裝

2022-10-17 10:28:05

Web 組件代碼

2023-06-01 08:24:08

OpenAIChatGPTPython

2021-04-07 13:38:27

Django項目視圖
點贊
收藏

51CTO技術(shù)棧公眾號