Package Management: Cargo vs NuGet
包管理:Cargo 与 NuGet 对照
What you’ll learn:
Cargo.tomlvs.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
常用命令对照
| Task | C# Command | Rust Command |
|---|---|---|
| Restore packages 恢复依赖 | dotnet restoredotnet restore | cargo fetchcargo fetch |
| Add package 新增包 | dotnet add package Newtonsoft.Jsondotnet add package Newtonsoft.Json | cargo add serde_jsoncargo add serde_json |
| Remove package 删除包 | dotnet remove package Newtonsoft.Jsondotnet remove package Newtonsoft.Json | cargo remove serde_jsoncargo remove serde_json |
| Update packages 更新依赖 | dotnet updatedotnet update | cargo updatecargo update |
| List packages 列出依赖 | dotnet list packagedotnet list package | cargo treecargo tree |
| Audit security 安全审计 | dotnet list package --vulnerabledotnet list package --vulnerable | cargo auditcargo audit |
| Clean build 清理构建 | dotnet cleandotnet clean | cargo cleancargo 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
Popular Crates for C# Developers
适合 C# 开发者的常见 crate
| C# Library | Rust Crate | Purpose |
|---|---|---|
| Newtonsoft.Json Newtonsoft.Json | serde_jsonserde_json | JSON serialization JSON 序列化 |
| HttpClient HttpClient | reqwestreqwest | HTTP client HTTP 客户端 |
| Entity Framework Entity Framework | diesel / sqlxdiesel / sqlx | ORM / SQL toolkit ORM / SQL 工具箱 |
| NLog/Serilog NLog / Serilog | log + env_loggerlog + env_logger | Logging 日志 |
| xUnit/NUnit xUnit / NUnit | Built-in #[test]内建 #[test] | Unit testing 单元测试 |
| Moq Moq | mockallmockall | Mocking Mock |
| Flurl Flurl | urlurl | URL manipulation URL 处理 |
| Polly Polly | towertower | 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()ispub
A 行:✅ 能编译,因为menu()是pub。 - Line B: ❌ Compile error —
secret_recipe()is private tokitchen
B 行:❌ 编译失败,因为secret_recipe()只在kitchen模块内部可见。 - Line C: ✅ Compiles —
staff::cook()ispub, andcook()can accesssecret_recipe()viasuper::(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 那种“只是提醒一下”的私有约定是两回事。