<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Gopher的Rust第一課:Rust的依賴管理

          共 46698字,需瀏覽 94分鐘

           ·

          2024-06-21 11:09

          在上一章《Gopher的Rust第一課:Rust代碼組織》中,我們了解了Rust的代碼組織形式,知道了基于Cargo構(gòu)建項(xiàng)目以及Rust代碼組織是目前的標(biāo)準(zhǔn)方式,同時(shí)Cargo也是管理項(xiàng)目外部依賴的標(biāo)準(zhǔn)方法,而項(xiàng)目內(nèi)部的代碼組織則由Rust module來完成。

          在這一章中,我們將聚焦Rust的依賴管理,即Cargo對外部crate依賴的管理操作。我將先介紹幾種依賴的來源類型(來自crates.io或其他Package Registries、來自某個(gè)git倉庫以及來自本地的crate等),然后說說Cargo依賴的常見操作,包括依賴的添加、升降版本和刪除;最后,聊一下如何處理依賴同一個(gè)依賴項(xiàng)的不同版本。

          作為Gopher,我們先來簡略回顧一下Go的依賴管理要點(diǎn),大家可以在學(xué)習(xí)Cargo依賴管理后自己做個(gè)簡單的對比,看看各自的優(yōu)缺點(diǎn)是什么。

          5.1 Go依賴管理回顧

          Go 1.11版本[2]開始,Go引入了Go Modules[3]以替代舊的GOPATH方式進(jìn)行依賴管理。

          我們可以使用go mod init命令初始化一個(gè)新的Go模塊。go mod init會(huì)創(chuàng)建一個(gè)go.mod文件,該文件記錄了當(dāng)前項(xiàng)目的模塊路徑,并通過require directive記錄了當(dāng)前模塊的依賴項(xiàng)以及版本:

          require github.com/some/module v1.2.3

          在開發(fā)過程中,我們也可以使用replace替換某個(gè)模塊的路徑,例如將依賴指向本地代碼庫進(jìn)行調(diào)試:

          replace example.com/some/module => ../local/module

          或是通過replace將依賴指向某個(gè)特定版本的包。Go 1.18[4]引入的Go工作區(qū)模式[5]讓依賴本地包的動(dòng)作更為便利絲滑。

          Go Modules支持語義版本控制(semver),版本號格式為vX.Y.Z(其中X是major,Y為minor,Z為patch)。當(dāng)發(fā)生不兼容變化時(shí)X編號需要+1。Go創(chuàng)新性地使用了語義版本導(dǎo)入機(jī)制,通過在包導(dǎo)入路徑上使用vX來支持導(dǎo)入同一個(gè)包的不同major版本:

          import (
           "github.com/some/module"
           v2 "github.com/some/module/v2"
          )

          無論是Go代碼中引入新依賴,還是通過go mod edit命令手工修改依賴(升級、更新版本或降級版本),通過go mod tidy這個(gè)萬能命令都可以自動(dòng)清理和整理依賴。go module還支持使用go.sum文件來記錄每個(gè)依賴項(xiàng)的精確版本和校驗(yàn)和,確保依賴的完整性和安全性。go.sum文件應(yīng)當(dāng)提交到版本控制系統(tǒng)中。

          此外,go mod vendor支持將依賴項(xiàng)副本存儲(chǔ)在本地,這可以使你的項(xiàng)目在沒有網(wǎng)絡(luò)連接的情況下構(gòu)建,并且可以避免依賴項(xiàng)版本沖突。

          Go并沒有采用像Rust、Js那樣的中心module registry,而是采用了分布式go proxy來實(shí)現(xiàn)依賴發(fā)現(xiàn)與獲取,默認(rèn)的goproxy為proxy.golang.org,國內(nèi)Gopher可以使用goproxy.cn、goproxy.io以及幾個(gè)大廠提供的GOPROXY。

          注:更多關(guān)于Go module依賴管理的系統(tǒng)且詳細(xì)的內(nèi)容,可以看看我在極客時(shí)間“Go語言第一課”專欄[6]中的兩講:06|構(gòu)建模式:Go是怎么解決包依賴管理問題的?[7]07|構(gòu)建模式:Go Module的6類常規(guī)操作[8]

          接下來,我們正式進(jìn)入Rust的依賴管理環(huán)節(jié),我們先來看看Cargo依賴的來源。

          5.2 Cargo依賴的來源

          Rust的依賴管理系統(tǒng)中,Rust項(xiàng)目主要有以下幾種依賴來源:

          1. 來自crates.io的依賴:這是Rust官方的crate registry,包含了大量開源的Rust庫。
          2. 來自某個(gè)git倉庫的依賴:可以從任何git倉庫添加依賴,特別是在開發(fā)階段或使用未發(fā)布的版本時(shí)非常有用。
          3. 來自本地的crate依賴:可以添加本地文件系統(tǒng)中的crate,便于在開發(fā)過程中引用本地代碼。

          接下來,我們就來逐一看看在一個(gè)Cargo項(xiàng)目中如何配置這三種不同來源的依賴。

          5.2.1 來自crates.io的依賴

          在Rust中,最常見的依賴來源是crates.io,這也是Rust官方維護(hù)的中心crate registry,我們可以通過cargo命令或手工修改Cargo.toml文件來添加這些依賴。我們用一個(gè)示例來說明一下如何為當(dāng)前項(xiàng)目添加來自crates.io的依賴。

          我們先用cargo創(chuàng)建一個(gè)名為hello_world的binary項(xiàng)目:

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

          $cat 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]

          //managing-deps/hello_world/src/main.rs
          fn main() {
              println!("Hello, world!");
          }

          構(gòu)建該項(xiàng)目,這與我們在《Gopher的Rust第一課:第一個(gè)Rust程序[9]》一文中描述的別無二致:

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

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

          現(xiàn)在我們改造一下main.rs代碼,添加點(diǎn)“實(shí)用”代碼(改自serde的example):

          //managing-deps/hello_world/src/main.rs
          use serde::{Deserialize, Serialize};

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

          fn main() {
              println!("Hello, world!");
              let point = Point { x: 1, y: 2 };

              // Convert the Point to a JSON string.
              let serialized = serde_json::to_string(&point).unwrap();

              // Prints serialized = {"x":1,"y":2}
              println!("serialized = {}", serialized);

              // Convert the JSON string back to a Point.
              let deserialized: Point = serde_json::from_str(&serialized).unwrap();

              // Prints deserialized = Point { x: 1, y: 2 }
              println!("deserialized = {:?}", deserialized);
          }

          然后我們通過cargo check命令檢查一下源碼是否可以編譯通過:

          $cargo check
              Checking hello_world v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/hello_world)
          error[E0432]: unresolved import `serde`
           --> src/main.rs:1:5
            |
          1 | use serde::{Deserialize, Serialize};
            |     ^^^^^ use of undeclared crate or module `serde`

          error[E0433]: failed to resolve: use of undeclared crate or module `serde_json`
            --> src/main.rs:14:22
             |
          14 |     let serialized = serde_json::to_string(&point).unwrap();
             |                      ^^^^^^^^^^ use of undeclared crate or module `serde_json`

          error[E0433]: failed to resolve: use of undeclared crate or module `serde_json`
            --> src/main.rs:20:31
             |
          20 |     let deserialized: Point = serde_json::from_str(&serialized).unwrap();
             |                               ^^^^^^^^^^ use of undeclared crate or module `serde_json`

          Some errors have detailed explanations: E0432, E0433.
          For more information about an error, try `rustc --explain E0432`.
          error: could not compile `hello_world` (bin "hello_world") due to 3 previous errors

          cargo check提示找不到serde、serde_json兩個(gè)crate。并且,cargo check執(zhí)行后,多出一個(gè)Cargo.lock文件。由于此時(shí)尚未在Cargo.toml中添加依賴(雖然代碼中明確了對serde和serde_json的依賴),Cargo.lock中還沒有依賴package的具體信息:

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

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

          Rust是否可以像go module那樣通過go mod tidy自動(dòng)掃描源碼并在Cargo.toml中補(bǔ)全依賴信息呢?然而并沒有。Rust添加依賴的操作還是需要手動(dòng)完成。

          我們的rust源碼依賴serde和serde_json,接下來,我們就需要在Cargo.toml中手工添加serde、serde_json依賴,當(dāng)然最標(biāo)準(zhǔn)的方法還是通過cargo add命令:

          $cargo add serde serde_json
                Adding serde v1.0.202 to dependencies.
                       Features:
                       + std
                       - alloc
                       - derive
                       - rc
                       - serde_derive
                       - unstable
                Adding serde_json v1.0.117 to dependencies.
                       Features:
                       + std
                       - alloc
                       - arbitrary_precision
                       - float_roundtrip
                       - indexmap
                       - preserve_order
                       - raw_value
                       - unbounded_depth

          我們查看一下cargo add執(zhí)行后的Cargo.toml:

          $cat 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]
          serde = "1.0.202"
          serde_json = "1.0.117"

          我們看到在dependencies下新增了兩個(gè)直接依賴信息:serde和serde_json以及它們的版本信息。

          關(guān)于依賴版本,Cargo定義了的兼容性規(guī)則[10]如下:

          針對在1.0版本之前的版本,比如0.x.y,語義版本規(guī)范[11]認(rèn)為是處于初始開發(fā)階段,公共API是不穩(wěn)定的,因此沒有明確兼容性語義。但Cargo對待這樣的版本的規(guī)則是:0.x.y與0.x.z是兼容的,如果x > 0且y >=z。比如:0.1.10是兼容0.1.1的。而在1.0版本之后,Cargo參考語義版本規(guī)范確定版本兼容性。

          基于上述的兼容性規(guī)則,在Cargo.toml中指定依賴版本的形式與語義有如下幾種情況:

          some_crate = "1.2.3" => 版本范圍[1.2.3, 2.0.0)。
          some_crate = "1.2" => 版本范圍[1.2.0, 2.0.0)。
          some_crate = "1" => 版本范圍[1.0.0, 2.0.0)。

          some_crate = "0.2.3" => 版本范圍[0.2.3, 0.3.0)。
          some_crate = "0.2" => 版本范圍[0.2.0, 0.3.0)。
          some_crate = "0" => 版本范圍[0.0.0, 1.0.0)。

          some_crate = "0.0" => 版本范圍[0.0.0, 0.1.0)。
          some_crate = "0.0.3" => 版本范圍[0.0.3, 0.0.4)。

          some_crate = "^1.2.3" => 版本范圍[1.2.3]。

          some_crate = "~1.2.3" => 版本范圍[1.2.3, 1.3.0)。
          some_crate = "~1.2" => 版本范圍[1.2.0, 1.3.0)。
          some_crate = "~1" => 版本范圍[1.0.0, 2.0.0)。

          Cargo還支持一些帶有通配符的版本需求形式:

          some_crate = "*" => 版本范圍[0.0.0, )。
          some_crate = "1.*" => 版本范圍[1.0.0, 2.0.0)。
          some_crate = "1.2.*" => 版本范圍[1.2.0, 1.3.0)。

          如果要限制最高版本范圍,可以用帶有多版本的需求形式:

          some_crate = ">=1.2, < 1.5" => 版本范圍[1.2.0, 1.5.0)。

          有了版本范圍后,Cargo初始就會(huì)使用該范圍內(nèi)的當(dāng)前最大版本號版本作為依賴的最終版本。比如some_crate = "1.2.3",但當(dāng)前some_crate的最高版本為1.3.5,那么Cargo會(huì)選擇1.3.5的some_crate作為當(dāng)前項(xiàng)目的依賴。

          如果一個(gè)項(xiàng)目有兩個(gè)依賴項(xiàng)同時(shí)依賴另外一個(gè)共同的依賴,比如(例子來自Cargo book):

          # Package A
          [dependencies]
          bitflags = "1.0"

          # Package B
          [dependencies]
          bitflags = "1.1"

          那么A依賴bitflags的范圍在[1.0.0, 2.0.0),B依賴bitflags的范圍在[1.1.0, 2.0.0),這樣如果當(dāng)前bitflags的最新版本為1.2.1,那么Cargo會(huì)選擇1.2.1作為bitflags的最終版本。這點(diǎn)與Go的最小版本選擇(mvs)[12]是不一樣的,在這個(gè)示例情況下,Go會(huì)選擇bitflags的1.1.0版本,即滿足A和B的bitflags的最小版本即可。

          后續(xù)當(dāng)依賴的版本有更新時(shí),可以執(zhí)行cargo update升級依賴的版本到一個(gè)兼容的、更高的版本(體現(xiàn)在Cargo.lock文件中依賴的版本更新)。

          Cargo.lock是鎖定Cargo最終采用的依賴的版本的描述文件,這個(gè)文件由cargo管理,不要手動(dòng)修改,這時(shí)的Cargo.lock文件如下:

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

          [[package]]
          name = "hello_world"
          version = "0.1.0"
          dependencies = [
           "serde",
           "serde_json",
          ]

          [[package]]
          name = "itoa"
          version = "1.0.11"
          source = "registry+https://github.com/rust-lang/crates.io-index"
          checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"

          [[package]]
          name = "proc-macro2"
          version = "1.0.83"
          source = "registry+https://github.com/rust-lang/crates.io-index"
          checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43"
          dependencies = [
           "unicode-ident",
          ]

          [[package]]
          name = "quote"
          version = "1.0.36"
          source = "registry+https://github.com/rust-lang/crates.io-index"
          checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
          dependencies = [
           "proc-macro2",
          ]

          [[package]]
          name = "ryu"
          version = "1.0.18"
          source = "registry+https://github.com/rust-lang/crates.io-index"
          checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"

          [[package]]
          name = "serde"
          version = "1.0.202"
          source = "registry+https://github.com/rust-lang/crates.io-index"
          checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
          dependencies = [
           "serde_derive",
          ]

          [[package]]
          name = "serde_derive"
          version = "1.0.202"
          source = "registry+https://github.com/rust-lang/crates.io-index"
          checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
          dependencies = [
           "proc-macro2",
           "quote",
           "syn",
          ]

          [[package]]
          name = "serde_json"
          version = "1.0.117"
          source = "registry+https://github.com/rust-lang/crates.io-index"
          checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
          dependencies = [
           "itoa",
           "ryu",
           "serde",
          ]

          [[package]]
          name = "syn"
          version = "2.0.65"
          source = "registry+https://github.com/rust-lang/crates.io-index"
          checksum = "d2863d96a84c6439701d7a38f9de935ec562c8832cc55d1dde0f513b52fad106"
          dependencies = [
           "proc-macro2",
           "quote",
           "unicode-ident",
          ]

          [[package]]
          name = "unicode-ident"
          version = "1.0.12"
          source = "registry+https://github.com/rust-lang/crates.io-index"
          checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"

          和go.sum類似(但go.sum并不指示依賴項(xiàng)采用的具體版本), Cargo.lock中對于每個(gè)依賴項(xiàng)都包括名字、具體某個(gè)版本、來源與校驗(yàn)和。

          我們再用cargo check一下該項(xiàng)目是否可以編譯成功:

          $cargo check
             Compiling serde v1.0.202
             Compiling serde_json v1.0.117
              Checking ryu v1.0.18
              Checking itoa v1.0.11
              Checking hello_world v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/hello_world)
          error: cannot find derive macro `Serialize` in this scope
           --> src/main.rs:3:10
            |
          3 | #[derive(Serialize, Deserialize, Debug)]
            |          ^^^^^^^^^
            |
          note: `Serialize` is imported here, but it is only a trait, without a derive macro
           --> src/main.rs:1:26
            |
          1 | use serde::{Deserialize, Serialize};
            |                          ^^^^^^^^^

          error: cannot find derive macro `Deserialize` in this scope
           --> src/main.rs:3:21
            |
          3 | #[derive(Serialize, Deserialize, Debug)]
            |                     ^^^^^^^^^^^
            |
          note: `Deserialize` is imported here, but it is only a trait, without a derive macro
           --> src/main.rs:1:13
            |
          1 | use serde::{Deserialize, Serialize};
            |             ^^^^^^^^^^^

          error[E0277]: the trait bound `Point: Serialize` is not satisfied
              --> src/main.rs:14:44
               |
          14   |     let serialized = serde_json::to_string(&point).unwrap();
               |                      --------------------- ^^^^^^ the trait `Serialize` is not implemented for `Point`
               |                      |
               |                      required by a bound introduced by this call
               |
               = help: the following other types implement trait `Serialize`:
                         bool
                         char
                         isize
                         i8
                         i16
                         i32
                         i64
                         i128
                       and 131 others
          note: required by a bound in `serde_json::to_string`
              --> /Users/tonybai/.cargo/registry/src/rsproxy.cn-8f6827c7555bfaf8/serde_json-1.0.117/src/ser.rs:2209:17
               |
          2207 | pub fn to_string<T>(value: &T) -> Result<String>
               |        --------- required by a bound in this function
          2208 | where
          2209 |     T: ?Sized + Serialize,
               |                 ^^^^^^^^^ required by this bound in `to_string`

          error[E0277]: the trait bound `Point: Deserialize<'_>` is not satisfied
              --> src/main.rs:20:31
               |
          20   |     let deserialized: Point = serde_json::from_str(&serialized).unwrap();
               |                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Deserialize<'
          _>` is not implemented for `Point`
               |
               = help: the following other types implement trait `Deserialize<'de>`:
                         bool
                         char
                         isize
                         i8
                         i16
                         i32
                         i64
                         i128
                       and 142 others
          note: required by a bound in `serde_json::from_str`
              --> /Users/tonybai/.cargo/registry/src/rsproxy.cn-8f6827c7555bfaf8/serde_json-1.0.117/src/de.rs:2676:8
               |
          2674 | pub fn from_str<'
          a, T>(s: &'a str) -> Result<T>
               |        -------- required by a bound in this function
          2675 | where
          2676 |     T: de::Deserialize<'
          a>,
               |        ^^^^^^^^^^^^^^^^^^^ required by this bound in `from_str`

          For more information about this error, try `rustc --explain E0277`.
          error: could not compile `hello_world` (bin "hello_world") due to 4 previous errors

          似乎是依賴包缺少某個(gè)feature。我們重新add一下serde依賴,這次帶著必要的feature:

          $cargo add serde --features derive,serde_derive
                Adding serde v1.0.202 to dependencies.
                       Features:
                       + derive
                       + serde_derive
                       + std
                       - alloc
                       - rc
                       - unstable

          然后再執(zhí)行check:

          $cargo check
             Compiling proc-macro2 v1.0.83
             Compiling unicode-ident v1.0.12
             Compiling serde v1.0.202
             Compiling quote v1.0.36
             Compiling syn v2.0.65
             Compiling serde_derive v1.0.202
              Checking serde_json v1.0.117
              Checking hello_world v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/hello_world)
              Finished dev [unoptimized + debuginfo] target(s) in 8.50s

          我們看到,當(dāng)開啟serde的derive和serde_derive feature后,項(xiàng)目代碼就可以正常編譯和運(yùn)行了,下面是運(yùn)行結(jié)果:

          $cargo run
             Compiling itoa v1.0.11
             Compiling ryu v1.0.18
             Compiling serde v1.0.202
             Compiling serde_json v1.0.117
             Compiling hello_world v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/hello_world)
              Finished dev [unoptimized + debuginfo] target(s) in 4.16s
               Running `target/debug/hello_world`
          Hello, world!
          serialized = {"x":1,"y":2}
          deserialized = Point { x: 1, y: 2 }

          注:feature是cargo提供的一種條件編譯和選項(xiàng)依賴的機(jī)制,有些類似于Go build constraints[13],但表達(dá)能力和控制精細(xì)度要遠(yuǎn)超go build constraints,但其復(fù)雜度也遠(yuǎn)超go build constraints。在本章中,我們不對feature進(jìn)行展開說明,更多關(guān)于feature的詳細(xì)說明,請參見cargo feature參考手冊[14]

          除了官方的crates.io,Cargo還支持來自其他非官方的Registry的依賴,比如使用企業(yè)私有crate registry,這個(gè)不在本章內(nèi)容范圍內(nèi),后續(xù)會(huì)考慮用專題的形式說明。

          考慮crates.io在海外,國內(nèi)Rustaceans可以考慮使用國內(nèi)的crate源[15],比如使用rsproxy源的配置如下:

          // ~/.cargo/config
          [source.crates-io]
          replace-with = 'rsproxy'

          [source.rsproxy]
          registry = "https://rsproxy.cn/crates.io-index"

          [source.rsproxy-sparse]
          registry = "sparse+https://rsproxy.cn/index/"

          [registries.rsproxy]
          index = "https://rsproxy.cn/crates.io-index"

          [net]
          git-fetch-with-cli = true

          git-fetch-with-cli = true表示使用本地git命令去獲取registry index,否則使用內(nèi)置的git庫來獲取。

          5.2.2 來自git倉庫的依賴

          有時(shí)候,我們可能需要依賴一個(gè)尚未發(fā)布到crates.io上的庫,這時(shí)可以通過git倉庫來添加依賴。當(dāng)然,這一方式也非常適合一些企業(yè)內(nèi)的私有g(shù)it倉庫上的依賴。在Go中,如果沒有一些額外的IT設(shè)置支持,便很難拉取私有倉庫上的go module[16]

          下面我們使用下面命令將Cargo.toml中的serde依賴改為從git repo獲取:

          $cargo add serde --features derive,serde_derive  --git https://github.com/serde-rs/serde.git
              Updating git repository `https://github.com/serde-rs/serde.git`
                Adding serde (git) to dependencies.
                       Features:
                       + derive
                       + serde_derive
                       + std
                       - alloc
                       - rc
                       - unstable

          更新后的Cargo.toml依賴列表變?yōu)榱耍?/p>

          [dependencies]
          serde = { git = "https://github.com/serde-rs/serde.git", version = "1.0.202", features = ["derive""serde_derive"] }
          serde_json = "1.0.117"

          不過當(dāng)我執(zhí)行cargo check時(shí)報(bào)如下錯(cuò)誤:

          $cargo check
              Updating git repository `https://github.com/serde-rs/serde.git`
          remote: Enumerating objects: 28491, done.
          remote: Counting objects: 100% (6879/6879), done.
          remote: Compressing objects: 100% (763/763), done.
          remote: Total 28491 (delta 6255), reused 6560 (delta 6111), pack-reused 21612
          Receiving objects: 100% (28491/28491), 7.97 MiB | 205.00 KiB/s, done.
          Resolving deltas: 100% (20065/20065), done.
          From https://github.com/serde-rs/serde
           * [new ref]                    -> origin/HEAD
           * [new tag]           v0.2.0     -> v0.2.0
           * [new tag]           v0.2.1     -> v0.2.1
           * [new tag]           v0.3.0     -> v0.3.0
           * [new tag]           v0.3.1     -> v0.3.1
           ... ...
           * [new tag]           v1.0.98    -> v1.0.98
           * [new tag]           v1.0.99    -> v1.0.99
             Compiling serde v1.0.202
             Compiling serde_derive v1.0.202 (https://github.com/serde-rs/serde.git#37618545)
             Compiling serde v1.0.202 (https://github.com/serde-rs/serde.git#37618545)
              Checking serde_json v1.0.117
              Checking hello_world v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/hello_world)
          error[E0277]: the trait bound `Point: serde::ser::Serialize` is not satisfied
              --> src/main.rs:14:44
          ... ...

          在serde的github issue中,這個(gè)問題似乎已經(jīng)修正[17],但在我的環(huán)境下不知何故依舊存在。

          在使用git來源時(shí),我們也可以指定一個(gè)特定的分支、tag或者commit:

          [dependencies]
          serde = { git = "https://github.com/serde-rs/serde.git", branch = "next" }
          # 或者
          serde = { git = "https://github.com/serde-rs/serde.git", tag = "v1.0.104" }
          # 或者
          serde = { git = "https://github.com/serde-rs/serde.git", rev = "a1b2c3d4" }

          5.2.3 來自本地的crate依賴

          在開發(fā)過程中,我們還可能需要引用本地文件系統(tǒng)中的crate。在Go中,我們可以使用go mod的replace或者Go workspace來解決該問題。在Rust中,我們也可以通過下面方式來添加本地依賴:

          $cargo add serde --features derive,serde_derive  --path ../serde/serde
                Adding serde (local) to dependencies.
                       Features:
                       + derive
                       + serde_derive
                       + std
                       - alloc
                       - rc
                       - unstable

          // Cargo.toml
          [dependencies]
          serde = { version = "1.0.202", features = ["derive""serde_derive"], path = "../serde/serde" }

          不過,和來自git一樣,基于來自本地的crate依賴,cargo check也報(bào)和基于git的crate依賴同樣的錯(cuò)誤。

          5.3 Cargo依賴常見操作

          下面簡要說說依賴的常見操作,以來自crates.io的依賴為例。

          5.3.1 添加依賴

          正如上面示例中我們演示的那樣,我們可以通過cargo add來添加一個(gè)依賴,或者可以通過手工編輯Cargo.toml文件添加對應(yīng)的配置。例如,添加一個(gè)源自crates.io的新依賴rand庫:

          [dependencies]
          rand = "0.8"

          5.3.2 升降版本

          要升級某個(gè)依賴到兼容的最新版本,可以使用cargo update;如果升級到不兼容版本,需要先修改Cargo.toml中的版本需求。例如,將rand庫升級到2.x版本:

          [dependencies]
          rand = "2.0"

          然后運(yùn)行cargo update,Cargo會(huì)根據(jù)新的版本號需求進(jìn)行重新解析依賴。

          當(dāng)然要降級依賴的版本到一個(gè)兼容的版本,通常可能需要在版本需求中使用類似“^x.y.z”來精確指定版本;如果要降級到一個(gè)不兼容版本,和升級到不兼容版本一樣,需要先修改Cargo.toml中的版本需求,然后運(yùn)行cargo update,Cargo會(huì)根據(jù)新的版本號需求進(jìn)行重新解析依賴。

          5.3.3 刪除依賴

          刪除一個(gè)依賴則十分容易,只需從Cargo.toml中移除或注釋掉對應(yīng)的依賴配置, 然后運(yùn)行cargo build,Cargo會(huì)更新項(xiàng)目的依賴關(guān)系。

          5.4 處理依賴同一個(gè)依賴項(xiàng)的不同版本

          在某些情況下,不同的crate可能依賴同一個(gè)crate的不同版本,這也是編程語言中典型的鉆石依賴問題!是一個(gè)常見的依賴管理挑戰(zhàn)。它發(fā)生在一個(gè)依賴項(xiàng)被兩個(gè)或更多其他依賴項(xiàng)共享時(shí)。比如:app依賴A、B ,而A、B又同時(shí)依賴C。

          在這樣的情況下,前面我們提過Go給出的解決方案包含三點(diǎn):

          • 若A、B依賴的C的版本相同,那么選取這個(gè)相同的C版本即可;
          • 若A、B依賴的C的版本不同但兼容(依照semver規(guī)范),那么選取C滿足A、B依賴的最小版本,這叫做最小版本選擇;
          • 若A、B依賴的C的版本不同且不兼容,那么通過語義導(dǎo)入版本[18],最終app將導(dǎo)入C的不同版本,這兩個(gè)版本將在app中共存。

          那么在Rust項(xiàng)目中,Cargo又是如何處理的呢?我們通過一個(gè)示例分別來看看這三種情況,我們創(chuàng)建一個(gè)app的示例:

          // 在rust-guide-for-gopher/managing-deps目錄下
          $tree -F app
          app
          ├── A/
          │   ├── Cargo.toml
          │   └── src/
          │       └── lib.rs
          ├── B/
          │   ├── Cargo.toml
          │   └── src/
          │       └── lib.rs
          ├── C/
          │   ├── Cargo.lock
          │   ├── Cargo.toml
          │   └── src/
          │       └── lib.rs
          ├── Cargo.lock
          ├── Cargo.toml
          └── src/
              └── main.rs

          7 directories, 10 files

          app是一個(gè)binary cargo project,它的Cargo.toml和src/main.rs內(nèi)容如下:

          // app/Cargo.toml
          [package]
          name = "app"
          version = "0.1.0"
          edition = "2021"

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

          [dependencies]
          A = { path = "./A", version = "0.1.0" }
          B = { path = "./B", version = "0.1.0" }

          // app/src/main.rs
          fn main() {
              println!("Hello, world!");
              A::hello_from_a();
              B::hello_from_b();
          }

          我們看到:app依賴crate A和B,并且分別調(diào)用了兩個(gè)crate的公共函數(shù)。

          接下來,我們再來看看A和B的情況,我們分場景說明。

          5.4.1 依賴C的相同版本

          當(dāng)A和B依賴C的相同版本時(shí),這個(gè)不難推斷cargo最終會(huì)為A和B選擇同一個(gè)依賴C的版本。比如:

          $cat A/Cargo.toml
          [package]
          name = "A"
          version = "0.1.0"
          edition = "2021"

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

          [dependencies]
          C = { path = "../C", version = "1.0.0" }


          $cat B/Cargo.toml
          [package]
          name = "B"
          version = "0.1.0"
          edition = "2021"

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

          [dependencies]
          C = { path = "../C", version = "1.0.0" }

          $cat A/src/lib.rs
          pub fn hello_from_a() {
              println!("Hello from A begin");
              C::hello_from_c();
              println!("Hello from A end");
          }

          $cat B/src/lib.rs
          pub fn hello_from_b() {
              println!("Hello from B begin");
              C::hello_from_c();
              println!("Hello from B end");
          }


          $cat C/Cargo.toml
          [package]
          name = "C"
          version = "1.3.0"
          edition = "2021"

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

          [dependencies]

          $cat C/src/lib.rs
          pub fn hello_from_c() {
              println!("Hello from C 1.3.0");
          }

          在這里A和B對C的依賴都是version = "1.0.0",通過前面的講解我們知道,這等價(jià)于C的版本范圍為[1.0.0, 2.0.0)。而C目前的版本為1.3.0,那么Cargo就會(huì)為A和B都選擇1.3.0版本的C。我們運(yùn)行一下這個(gè)app程序:

          $cargo run
          ... ...
          Hello, world!
          Hello from A begin
          Hello from C 1.3.0
          Hello from A end
          Hello from B begin
          Hello from C 1.3.0
          Hello from B end

          我們還可以通過cargo tree命令驗(yàn)證一下對A和B對C版本的依賴:

          $cargo tree --workspace --target all --all-features --invert C

          C v1.3.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app/C)
          ├── A v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app/A)
          │   └── app v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app)
          └── B v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app/B)
              └── app v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app)

          我們看到A和B都依賴了C的v1.3.0版本。

          5.4.2 依賴C的兩個(gè)兼容版本

          現(xiàn)在我們修改一下A和B對C的依賴版本需求:

          $cat A/Cargo.toml
          [package]
          name = "A"
          version = "0.1.0"
          edition = "2021"

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

          [dependencies]
          C = { path = "../C", version = "1.1.1" }

          $cat B/Cargo.toml
          [package]
          name = "B"
          version = "0.1.0"
          edition = "2021"

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

          [dependencies]
          C = { path = "../C", version = "1.2.3" }

          讓A對C的依賴需求為1.1.1,讓B依賴需求為1.2.3,這回我們再來運(yùn)行一下cargo run和cargo tree:

          $cargo run
          ... ...
          Hello, world!
          Hello from A begin
          Hello from C 1.3.0
          Hello from A end
          Hello from B begin
          Hello from C 1.3.0
          Hello from B end

          $cargo tree --workspace --target all --all-features --invert C

          C v1.3.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app/C)
          ├── A v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app/A)
          │   └── app v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app)
          └── B v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app/B)
              └── app v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app)

          由于1.1.1和1.2.3是兼容版本,因此Cargo選擇了兼容這兩個(gè)版本的C當(dāng)前的最高版本1.3.0。

          5.4.3 依賴C的兩個(gè)不兼容版本

          現(xiàn)在我們來試驗(yàn)一下當(dāng)A和B依賴的C版本不兼容時(shí),Cargo會(huì)為A和B選擇C的什么版本!由于是本地環(huán)境,我們無法在一個(gè)目錄下保存兩個(gè)C版本,因此我們copy一份當(dāng)前的C組件,將拷貝重命名為C-1.3.0,然后將C下面的Cargo.toml和src/lib.rs修改成下面的樣子:

          $cat C/Cargo.toml
          [package]
          name = "C"
          version = "2.4.0"
          edition = "2021"

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

          [dependencies]

          $cat C/src/lib.rs
          pub fn hello_from_c() {
              println!("Hello from C 2.4.0");
          }

          然后我們修改一下A和B的依賴,讓他們分別依賴C-1.3.0和C:

          $cat A/Cargo.toml
          [package]
          name = "A"
          version = "0.1.0"
          edition = "2021"

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

          [dependencies]
          C = { path = "../C-1.3.0", version = "1.1.1" }

          $cat B/Cargo.toml
          [package]
          name = "B"
          version = "0.1.0"
          edition = "2021"

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

          [dependencies]
          C = { path = "../C", version = "2.2.3" }

          我們再來運(yùn)行一下該app:

          $cargo run
          ... ...
          Hello, world!
          Hello from A begin
          Hello from C 1.3.0
          Hello from A end
          Hello from B begin
          Hello from C 2.4.0
          Hello from B end

          我們看到cargo為A選擇的版本是C v1.3.0,而為B選擇的C版本是C v2.4.0,也就是說C的兩個(gè)不兼容版本在app中可以同時(shí)存在。

          讓我們再來用cargo tree查看一下對C的依賴關(guān)系:

          $cargo tree --workspace --target all --all-features --invert C

          error: There are multiple `C` packages in your project, and the specification `C` is ambiguous.
          Please re-run this command with one of the following specifications:
            [email protected]
            [email protected]

          我們看到,cargo tree提示我們兩個(gè)版本不兼容,必須明確指明是要查看哪個(gè)C版本的依賴,那我們就分別按版本查看一下:

          $cargo tree --workspace --target all --all-features --invert [email protected]

          C v1.3.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app/C-1.3.0)
          └── A v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app/A)
              └── app v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app)

          $cargo tree --workspace --target all --all-features --invert [email protected]

          C v2.4.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app/C)
          └── B v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app/B)
              └── app v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/app)

          5.4.4 直接依賴C的不同版本

          在Go中我們可以通過語義導(dǎo)入版本實(shí)現(xiàn)在app中直接依賴同一個(gè)包的兩個(gè)不兼容版本:

          import (
           "github.com/user/repo"
           v2  "github.com/user/repo/v2"
          )

          在Rust中,是否也可以實(shí)現(xiàn)這一點(diǎn)?如果可以,又是如何實(shí)現(xiàn)的呢?答案是可以。至少我們可以通過使用Cargo的依賴別名功能來實(shí)現(xiàn)。我們建立一個(gè)名為dep_alias的示例,其目錄結(jié)構(gòu)如下:

          $tree -F dep_alias
          dep_alias
          ├── C/
          │   ├── Cargo.lock
          │   ├── Cargo.toml
          │   └── src/
          │       └── lib.rs
          ├── C-1.3.0/
          │   ├── Cargo.lock
          │   ├── Cargo.toml
          │   └── src/
          │       └── lib.rs
          ├── Cargo.lock
          ├── Cargo.toml
          └── src/
              └── main.rs

          5 directories, 9 files

          在這個(gè)示例中,app依賴C-1.3.0目錄下的C 1.3.0版本以及C目錄下的C 2.4.0版本,下面是app/Cargo.toml和app/src/main.rs的代碼:

          // rust-guide-for-gopher/managing-deps/dep_alias/Cargo.toml

          $cat Cargo.toml
          [package]
          name = "app"
          version = "0.1.0"
          edition = "2021"

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

          [dependencies]
          C_v1 = { path = "C-1.3.0", version = "1.0.0", package = "C" }
          C_v2 = { path = "C", version = "2.3.0", package = "C" }

          $cat src/main.rs

          $cat src/main.rs
          extern crate C_v1 as C_v1;
          extern crate C_v2 as C_v2;

          fn main() {
              C_v1::hello_from_c();
              C_v2::hello_from_c();
          }

          這里,我們?yōu)镃的兩個(gè)不兼容版本建立了兩個(gè)別名:C_v1和C_v2,然后在代碼中分別使用C_v1和C_v2,cargo會(huì)分別為C_v1和C_v2選擇合適的版本,這里C_v1最終選擇為1.3.0,而C_v2最終定為2.4.0:

          $cargo run 
          Hello from C 1.3.0
          Hello from C 2.4.0

          由于包名依然是C,所以在使用cargo tree查看依賴關(guān)系時(shí),依然要帶上不同版本:

          $cargo tree --workspace --target all --all-features --invert [email protected]
          C v1.3.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/dep_alias/C-1.3.0)
          └── app v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/dep_alias)

          $cargo tree --workspace --target all --all-features --invert [email protected]
          C v2.4.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/dep_alias/C)
          └── app v0.1.0 (/Users/tonybai/Go/src/github.com/bigwhite/experiments/rust-guide-for-gopher/managing-deps/dep_alias)

          5.5 小結(jié)

          在這一章中,我們介紹了Rust中通過Cargo進(jìn)行依賴管理的基本方法。

          我們首先簡要回顧了Go語言的依賴管理,特別是Go Modules的相關(guān)內(nèi)容,如go.mod文件、版本控制機(jī)制等。

          接著我們介紹了Rust中通過Cargo進(jìn)行依賴管理的方法。Cargo依賴主要有三種來源:crates.io官方注冊中心、Git倉庫和本地文件系統(tǒng)。通過Cargo.toml文件和cargo命令,我們可以靈活添加、升級、降級或刪除依賴項(xiàng)。文中還講解了Cargo的版本兼容性規(guī)則和各種指定版本的語法。

          針對依賴同一個(gè)庫的不同版本的情況,我通過示例說明了Cargo的處理方式:如果版本相同或兼容,Cargo會(huì)選擇滿足要求的當(dāng)前最高版本;如果版本不兼容,Cargo允許在項(xiàng)目中同時(shí)使用這些不兼容的版本,可以通過別名來區(qū)分使用。

          總體來看,Cargo提供的依賴管理方式表達(dá)能力很強(qiáng)大,但相對于Go來說,還是復(fù)雜了很多,學(xué)習(xí)起來曲線要高很多,troubleshooting起來也不易,文中尚有一個(gè)遺留問題尚未解決,如果大家有解決方案或思路,可以在文章評論中告知我,感謝。

          注:本文涉及的都是cargo依賴管理的基礎(chǔ)內(nèi)容,還有很多細(xì)節(jié)以及高級用法并未涉及。

          本章中涉及的源碼可以在這里[19]下載。


          5.6 參考資料

          • Cargo Book: Specifying Dependencies[20] - https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html
          • Cargo Book: Registries[21] - https://doc.rust-lang.org/cargo/reference/registries.html

          參考資料

          [1] 

          Gopher的Rust第一課:Rust代碼組織: https://tonybai.com/2024/06/06/gopher-rust-first-lesson-organizing-rust-code

          [2] 

          Go 1.11版本: https://tonybai.com/2018/11/19/some-changes-in-go-1-11/

          [3] 

          Go Modules: https://go.dev/ref/mod

          [4] 

          Go 1.18: https://tonybai.com/2022/04/20/some-changes-in-go-1-18

          [5] 

          Go工作區(qū)模式: https://tonybai.com/2021/11/12/go-workspace-mode-in-go-1-18

          [6] 

          極客時(shí)間“Go語言第一課”專欄: http://gk.link/a/10AVZ

          [7] 

          06|構(gòu)建模式:Go是怎么解決包依賴管理問題的?: https://time.geekbang.org/column/article/429941

          [8] 

          07|構(gòu)建模式:Go Module的6類常規(guī)操作: https://time.geekbang.org/column/article/431463

          [9] 

          Gopher的Rust第一課:第一個(gè)Rust程序: https://tonybai.com/2024/05/27/gopher-rust-first-lesson-first-rust-program/

          [10] 

          Cargo定義了的兼容性規(guī)則: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html

          [11] 

          語義版本規(guī)范: https://semver.org/

          [12] 

          1.0.0, 2.0.0),B依賴bitflags的范圍在[1.1.0, 2.0.0),這樣如果當(dāng)前bitflags的最新版本為1.2.1,那么Cargo會(huì)選擇1.2.1作為bitflags的最終版本。這點(diǎn)與Go的[最小版本選擇(mvs): https://research.swtch.com/vgo-mvs

          [13] 

          Go build constraints: https://pkg.go.dev/go/build#hdr-Build_Constraints

          [14] 

          cargo feature參考手冊: https://doc.rust-lang.org/cargo/reference/features.html

          [15] 

          使用國內(nèi)的crate源: https://doc.rust-lang.org/cargo/reference/source-replacement.html

          [16] 

          拉取私有倉庫上的go module: https://tonybai.com/2021/09/03/the-approach-to-go-get-private-go-module-in-house

          [17] 

          這個(gè)問題似乎已經(jīng)修正: https://github.com/serde-rs/serde/issues/2526

          [18] 

          語義導(dǎo)入版本: https://research.swtch.com/vgo-import

          [19] 

          這里: https://github.com/bigwhite/experiments/tree/master/rust-guide-for-gopher/managing-deps

          [20] 

          Cargo Book: Specifying Dependencies: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html

          [21] 

          Cargo Book: Registries: https://doc.rust-lang.org/cargo/reference/registries.html

          [22] 

          Gopher部落知識星球: https://public.zsxq.com/groups/51284458844544

          [23] 

          鏈接地址: https://m.do.co/c/bff6eed92687

          - END -



          推薦閱讀:

          6 個(gè)必須嘗試的將代碼轉(zhuǎn)換為引人注目的圖表的工具

          Go 1.23新特性前瞻

          Gopher的Rust第一課:第一個(gè)Rust程序

          Go早期是如何在Google內(nèi)部發(fā)展起來的

          2024 Gopher Meetup 武漢站活動(dòng)

          go 中更加強(qiáng)大的 traces

          「GoCN酷Go推薦」我用go寫了魔獸世界登錄器?

          Go區(qū)不大,創(chuàng)造神話,科目三殺進(jìn)來了


          想要了解Go更多內(nèi)容,歡迎掃描下方??關(guān)注公眾號,掃描 [實(shí)戰(zhàn)群]二維碼  ,即可進(jìn)群和我們交流~



          - 掃碼即可加入實(shí)戰(zhàn)群 -


          分享、在看與點(diǎn)贊Go 

          瀏覽 139
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产做受 91电影 | 久久夜色精品国产噜噜亚洲AV | 亚洲在线视频免费观看 | 人人草人人摸人人爽 | 国产女女在线观看 |