Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Package Management: Cargo vs NuGet
包管理:Cargo 与 NuGet 对照

What you’ll learn: Cargo.toml vs .csproj, version specifiers, Cargo.lock, feature flags for conditional compilation, and common Cargo commands mapped to their NuGet/dotnet equivalents.
本章将学到什么: Cargo.toml.csproj 的对应关系,版本约束写法,Cargo.lock 的作用,条件编译里的 feature flag,以及常见 Cargo 命令和 NuGet / dotnet 命令之间的映射。

Difficulty: 🟢 Beginner
难度: 🟢 入门

Dependency Declaration
依赖声明

C# NuGet Dependencies
C# 的 NuGet 依赖

<!-- MyApp.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  
  <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
  <PackageReference Include="Serilog" Version="3.0.1" />
  <PackageReference Include="Microsoft.AspNetCore.App" />
  
  <ProjectReference Include="../MyLibrary/MyLibrary.csproj" />
</Project>

Rust Cargo Dependencies
Rust 的 Cargo 依赖

# Cargo.toml
[package]
name = "my_app"
version = "0.1.0"
edition = "2021"

[dependencies]
serde_json = "1.0"               # From crates.io (like NuGet)
serde = { version = "1.0", features = ["derive"] }  # With features
log = "0.4"
tokio = { version = "1.0", features = ["full"] }

# Local dependencies (like ProjectReference)
my_library = { path = "../my_library" }

# Git dependencies
my_git_crate = { git = "https://github.com/user/repo" }

# Development dependencies (like test packages)
[dev-dependencies]
criterion = "0.5"               # Benchmarking
proptest = "1.0"               # Property testing

Version Management
版本管理

C# Package Versioning
C# 的包版本管理

<!-- Centralized package management (Directory.Packages.props) -->
<Project>
  <PropertyGroup>
    <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
  </PropertyGroup>
  
  <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
  <PackageVersion Include="Serilog" Version="3.0.1" />
</Project>

<!-- packages.lock.json for reproducible builds -->

Rust Version Management
Rust 的版本管理

# Cargo.toml - Semantic versioning
[dependencies]
serde = "1.0"        # Compatible with 1.x.x (>=1.0.0, <2.0.0)
log = "0.4.17"       # Compatible with 0.4.x (>=0.4.17, <0.5.0)
regex = "=1.5.4"     # Exact version
chrono = "^0.4"      # Caret requirements (default)
uuid = "~1.3.0"      # Tilde requirements (>=1.3.0, <1.4.0)

# Cargo.lock - Exact versions for reproducible builds (auto-generated)
[[package]]
name = "serde"
version = "1.0.163"
# ... exact dependency tree

Package Sources
包源

C# Package Sources
C# 的包源

<!-- nuget.config -->
<configuration>
  <packageSources>
    <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
    <add key="MyCompanyFeed" value="https://pkgs.dev.azure.com/company/_packaging/feed/nuget/v3/index.json" />
  </packageSources>
</configuration>

Rust Package Sources
Rust 的包源

# .cargo/config.toml
[source.crates-io]
replace-with = "my-awesome-registry"

[source.my-awesome-registry]
registry = "https://my-intranet:8080/index"

# Alternative registries
[registries]
my-registry = { index = "https://my-intranet:8080/index" }

# In Cargo.toml
[dependencies]
my_crate = { version = "1.0", registry = "my-registry" }

Common Commands Comparison
常用命令对照

TaskC# CommandRust Command
Restore packages
恢复依赖
dotnet restore
dotnet restore
cargo fetch
cargo fetch
Add package
新增包
dotnet add package Newtonsoft.Json
dotnet add package Newtonsoft.Json
cargo add serde_json
cargo add serde_json
Remove package
删除包
dotnet remove package Newtonsoft.Json
dotnet remove package Newtonsoft.Json
cargo remove serde_json
cargo remove serde_json
Update packages
更新依赖
dotnet update
dotnet update
cargo update
cargo update
List packages
列出依赖
dotnet list package
dotnet list package
cargo tree
cargo tree
Audit security
安全审计
dotnet list package --vulnerable
dotnet list package --vulnerable
cargo audit
cargo audit
Clean build
清理构建
dotnet clean
dotnet clean
cargo clean
cargo clean

Features: Conditional Compilation
Feature:条件编译

C# Conditional Compilation
C# 条件编译

#if DEBUG
    Console.WriteLine("Debug mode");
#elif RELEASE
    Console.WriteLine("Release mode");
#endif

// Project file features
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
    <DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>

Rust Feature Gates
Rust 的 Feature Gate

# Cargo.toml
[features]
default = ["json"]              # Default features
json = ["serde_json"]          # Feature that enables serde_json
xml = ["serde_xml"]            # Alternative serialization
advanced = ["json", "xml"]     # Composite feature

[dependencies]
serde_json = { version = "1.0", optional = true }
serde_xml = { version = "0.4", optional = true }
#![allow(unused)]
fn main() {
// Conditional compilation based on features
#[cfg(feature = "json")]
use serde_json;

#[cfg(feature = "xml")]
use serde_xml;

pub fn serialize_data(data: &MyStruct) -> String {
    #[cfg(feature = "json")]
    return serde_json::to_string(data).unwrap();
    
    #[cfg(feature = "xml")]
    return serde_xml::to_string(data).unwrap();
    
    #[cfg(not(any(feature = "json", feature = "xml")))]
    return "No serialization feature enabled".to_string();
}
}

Using External Crates
使用外部 crate

C# LibraryRust CratePurpose
Newtonsoft.Json
Newtonsoft.Json
serde_json
serde_json
JSON serialization
JSON 序列化
HttpClient
HttpClient
reqwest
reqwest
HTTP client
HTTP 客户端
Entity Framework
Entity Framework
diesel / sqlx
diesel / sqlx
ORM / SQL toolkit
ORM / SQL 工具箱
NLog/Serilog
NLog / Serilog
log + env_logger
log + env_logger
Logging
日志
xUnit/NUnit
xUnit / NUnit
Built-in #[test]
内建 #[test]
Unit testing
单元测试
Moq
Moq
mockall
mockall
Mocking
Mock
Flurl
Flurl
url
url
URL manipulation
URL 处理
Polly
Polly
tower
tower
Resilience patterns
弹性治理模式

Example: HTTP Client Migration
示例:HTTP 客户端迁移

// C# HttpClient usage
public class ApiClient
{
    private readonly HttpClient _httpClient;
    
    public async Task<User> GetUserAsync(int id)
    {
        var response = await _httpClient.GetAsync($"/users/{id}");
        var json = await response.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<User>(json);
    }
}
#![allow(unused)]
fn main() {
// Rust reqwest usage
use reqwest;
use serde::Deserialize;

#[derive(Deserialize)]
struct User {
    id: u32,
    name: String,
}

struct ApiClient {
    client: reqwest::Client,
}

impl ApiClient {
    async fn get_user(&self, id: u32) -> Result<User, reqwest::Error> {
        let user = self.client
            .get(&format!("https://api.example.com/users/{}", id))
            .send()
            .await?
            .json::<User>()
            .await?;
        
        Ok(user)
    }
}
}

Workspaces vs Monorepos
Workspace 与 Monorepo

Python Monorepo (typical)
Python 里的典型 Monorepo

# Python monorepo (various approaches, no standard)
myproject/
├── pyproject.toml           # Root project
├── packages/
│   ├── core/
│   │   ├── pyproject.toml   # Each package has its own config
│   │   └── src/core/...
│   ├── api/
│   │   ├── pyproject.toml
│   │   └── src/api/...
│   └── cli/
│       ├── pyproject.toml
│       └── src/cli/...
# Tools: poetry workspaces, pip -e ., uv workspaces — no standard

Rust Workspace
Rust Workspace

# Rust — Cargo.toml at root
[workspace]
members = [
    "core",
    "api",
    "cli",
]

# Shared dependencies across workspace
[workspace.dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
# Rust workspace structure — standardized, built into Cargo
myproject/
├── Cargo.toml               # Workspace root
├── Cargo.lock               # Single lock file for all crates
├── core/
│   ├── Cargo.toml            # [dependencies] serde.workspace = true
│   └── src/lib.rs
├── api/
│   ├── Cargo.toml
│   └── src/lib.rs
└── cli/
    ├── Cargo.toml
    └── src/main.rs
# Workspace commands
cargo build                  # Build everything
cargo test                   # Test everything
cargo build -p core          # Build just the core crate
cargo test -p api            # Test just the api crate
cargo clippy --all           # Lint everything

Key insight: Rust workspaces are first-class, built into Cargo. Python monorepos require third-party tools (poetry, uv, pants) with varying levels of support. In a Rust workspace, all crates share a single Cargo.lock, ensuring consistent dependency versions across the project.
关键认识:Rust 的 workspace 是 Cargo 一等公民,原生就支持。Python 的 monorepo 通常得靠 poetry、uv、pants 这类第三方工具,支持程度参差不齐。Rust workspace 里所有 crate 共用一个 Cargo.lock,因此整仓依赖版本始终一致。


Exercises
练习

🏋️ Exercise: Module Visibility 🏋️ 练习:模块可见性

Challenge: Given this module structure, predict which lines compile and which don’t:
挑战题:给定下面这个模块结构,判断哪几行可以编译,哪几行不行:

mod kitchen {
    fn secret_recipe() -> &'static str { "42 spices" }
    pub fn menu() -> &'static str { "Today's special" }

    pub mod staff {
        pub fn cook() -> String {
            format!("Cooking with {}", super::secret_recipe())
        }
    }
}

fn main() {
    println!("{}", kitchen::menu());             // Line A
    println!("{}", kitchen::secret_recipe());     // Line B
    println!("{}", kitchen::staff::cook());       // Line C
}
🔑 Solution 🔑 参考答案
  • Line A: ✅ Compiles — menu() is pub
    A 行:✅ 能编译,因为 menu()pub
  • Line B: ❌ Compile error — secret_recipe() is private to kitchen
    B 行:❌ 编译失败,因为 secret_recipe() 只在 kitchen 模块内部可见。
  • Line C: ✅ Compiles — staff::cook() is pub, and cook() can access secret_recipe() via super:: (child modules can access parent’s private items)
    C 行:✅ 能编译,因为 staff::cook()pub,而且子模块可以通过 super:: 访问父模块的私有成员。

Key takeaway: In Rust, child modules can see parent’s privates (like Python’s _private convention, but enforced). Outsiders cannot. This is the opposite of Python where _private is just a hint.
关键结论:在 Rust 里,子模块能看见父模块的私有项,这一点有点像 Python 里约定俗成的 _private,但 Rust 是编译器强制执行的。外部模块则完全看不到。这和 Python 那种“只是提醒一下”的私有约定是两回事。