跳到主要内容

数据类型(Data Types)

Rust 是静态类型(Static Typed)语言,这意味着在编译时就必须知道所有变量的类型。

标量类型

整型

长度有符号无符号
8-biti8u8
16-biti16u16
32-biti32u32
64-biti64u64
128-biti128u128
archisizeusize
fn main() {
let decimal = 98_222;
let hex = 0xff;
let octal = 0o77;
let binary = 0b1111_0000;
let byte = b'A';

println!("十进制: {}", decimal);
println!("十六进制: {}", hex);
}

浮点型

fn main() {
let x = 2.0; // f64,默认类型
let y: f32 = 3.0; // f32

println!("x: {}, y: {}", x, y);
}

布尔型

fn main() {
let t = true;
let f: bool = false;

println!("t: {}, f: {}", t, f);
}

字符类型(char)

Rust 的字符类型使用 char 关键字,表示一个 Unicode 标量值。

fn main() {
// 隐式类型推断
let c = 'z';
let z = 'ℤ';
let heart_eyed_cat = '😻';

// 显式类型声明
let letter: char = 'A';
let chinese: char = '中';
let emoji: char = '🦀';

println!("字符: {}, {}, {}", c, z, heart_eyed_cat);
println!("显式类型: {}, {}, {}", letter, chinese, emoji);

// char 类型的大小是 4 字节
println!("char 类型大小: {} 字节", std::mem::size_of::<char>());
}

字符类型的特点

  1. Unicode 支持char 类型可以表示任何 Unicode 字符
  2. 固定大小:每个 char 占用 4 字节(32 位)
  3. 单引号:字符字面量使用单引号 ',字符串使用双引号 "
fn main() {
let character: char = 'R';
let string: &str = "Rust";

// 字符和字符串的区别
println!("字符: {}", character);
println!("字符串: {}", string);

// 字符的一些方法
println!("是否为字母: {}", character.is_alphabetic());
println!("是否为数字: {}", character.is_numeric());
println!("转为小写: {}", character.to_lowercase().collect::<String>());
}

字符串类型(&str 和 String)

Rust 有两种主要的字符串类型:&str(字符串切片)和 String(拥有所有权的字符串)。

&str(字符串切片)

&str 是字符串切片的引用,是一个不可变引用,指向存储在某处的 UTF-8 编码字符串数据。

fn main() {
// 字符串字面量的类型是 &str
let greeting: &str = "Hello, world!";
let chinese: &str = "你好,世界!";

// 字符串字面量存储在程序的二进制文件中
println!("问候语: {}", greeting);
println!("中文: {}", chinese);

// &str 的大小信息
println!("&str 大小: {} 字节", std::mem::size_of::<&str>());
println!("字符串长度: {} 字节", greeting.len());
println!("字符数量: {}", greeting.chars().count());
}

String(拥有所有权的字符串)

String 是一个可增长、可变、拥有所有权的 UTF-8 编码字符串类型。

fn main() {
// 创建 String
let mut s1 = String::new(); // 空字符串
let s2 = String::from("Hello"); // 从字面量创建
let s3 = "World".to_string(); // 转换为 String

// String 是可变的
s1.push_str("Rust"); // 添加字符串
s1.push('!'); // 添加字符

println!("s1: {}", s1); // "Rust!"
println!("s2: {}", s2); // "Hello"
println!("s3: {}", s3); // "World"

// String 可以修改
let mut greeting = String::from("Hello");
greeting.push_str(", Rust!");
println!("修改后: {}", greeting); // "Hello, Rust!"
}

创建 String 的各种方法和使用习惯

在 Rust 中,有多种方式创建 String,每种都有其适用场景和社区习惯:

1. String::from() - 最常见的方式
fn main() {
// ✅ 最常见和推荐的方式
let s1 = String::from("hello");
let s2 = String::from("world");

// 优点:
// - 语义清晰:明确表示"从...创建String"
// - 性能好:直接分配内存
// - 社区标准:大多数 Rust 代码都这样写
println!("{} {}", s1, s2);
}
2. .to_string() - 转换方法
fn main() {
// ✅ 也很常见,特别是在转换场景中
let s1 = "hello".to_string();
let s2 = 42.to_string(); // 数字转字符串
let s3 = true.to_string(); // 布尔值转字符串

// 优点:
// - 通用性:任何实现了 Display trait 的类型都可以用
// - 链式调用:可以在方法链中使用
println!("{}, {}, {}", s1, s2, s3);
}
3. String::new() - 创建空字符串
fn main() {
// ✅ 创建空字符串的标准方式
let mut s = String::new();
s.push_str("Hello");
s.push(' ');
s.push_str("World");

// 适用场景:
// - 需要逐步构建字符串
// - 不知道最终内容的情况
println!("{}", s);
}
4. format! 宏 - 格式化创建
fn main() {
let name = "Alice";
let age = 30;

// ✅ 需要格式化时的首选方式
let message = format!("Hello, {}! You are {} years old.", name, age);

// 等价但更繁琐的写法:
let message2 = String::from("Hello, ") + name + "! You are " + &age.to_string() + " years old.";

println!("{}", message);
println!("{}", message2);
}
5. String::with_capacity() - 预分配容量
fn main() {
// ✅ 知道大概大小时的性能优化
let mut s = String::with_capacity(50); // 预分配50字节
s.push_str("This is a long string that we know will be around 50 characters");

// 优点:避免多次内存重新分配
println!("容量: {}, 长度: {}", s.capacity(), s.len());
}
6. 其他创建方式
fn main() {
// 从字符重复创建
let s1 = "a".repeat(5); // "aaaaa"
let s2 = String::from("x").repeat(3); // "xxx"

// 从字符向量创建
let chars: Vec<char> = vec!['h', 'e', 'l', 'l', 'o'];
let s3: String = chars.into_iter().collect();

// 从字节向量创建
let bytes = vec![104, 101, 108, 108, 111]; // "hello" 的 UTF-8 字节
let s4 = String::from_utf8(bytes).unwrap();

println!("{}, {}, {}, {}", s1, s2, s3, s4);
}

使用习惯和最佳实践

社区习惯统计(基于常见开源项目):
  1. String::from() - 约 60% 的使用场景

    let name = String::from("Alice");
    let path = String::from("/home/user");
  2. .to_string() - 约 25% 的使用场景

    let number_str = 42.to_string();
    let converted = some_str_ref.to_string();
  3. format!() - 约 10% 的使用场景

    let message = format!("Error: {}", error_msg);
  4. 其他方式 - 约 5% 的使用场景

选择指南:
fn main() {
// 1. 从字符串字面量创建 - 优先使用 String::from
let s1 = String::from("hello"); // ✅ 推荐
let s2 = "hello".to_string(); // ✅ 也可以,但 String::from 更常见

// 2. 从其他类型转换 - 使用 .to_string()
let num_str = 42.to_string(); // ✅ 推荐
let bool_str = true.to_string(); // ✅ 推荐

// 3. 格式化字符串 - 使用 format!
let formatted = format!("Value: {}", 42); // ✅ 推荐

// 4. 空字符串 - 使用 String::new()
let mut empty = String::new(); // ✅ 推荐

// 5. 已知大小 - 使用 String::with_capacity()
let mut large = String::with_capacity(1000); // ✅ 性能优化

println!("{}, {}, {}, {}, {}", s1, num_str, formatted, empty, large.capacity());
}

性能对比

use std::time::Instant;

fn main() {
let iterations = 1_000_000;

// String::from 性能测试
let start = Instant::now();
for _ in 0..iterations {
let _s = String::from("hello");
}
let string_from_time = start.elapsed();

// .to_string() 性能测试
let start = Instant::now();
for _ in 0..iterations {
let _s = "hello".to_string();
}
let to_string_time = start.elapsed();

println!("String::from: {:?}", string_from_time);
println!(".to_string(): {:?}", to_string_time);
// 通常 String::from 略快,但差异很小
}

实际项目中的使用模式

// 配置和常量
const DEFAULT_NAME: &str = "default";

struct Config {
name: String,
path: String,
}

impl Config {
fn new() -> Self {
Self {
name: String::from(DEFAULT_NAME), // ✅ 常见模式
path: String::from("/tmp"), // ✅ 常见模式
}
}

fn from_env() -> Self {
Self {
name: std::env::var("APP_NAME")
.unwrap_or_else(|_| DEFAULT_NAME.to_string()), // ✅ 转换模式
path: std::env::var("APP_PATH")
.unwrap_or_else(|_| String::from("/tmp")), // ✅ 默认值模式
}
}
}

// 错误处理
fn process_file(path: &str) -> Result<String, String> {
if path.is_empty() {
return Err(String::from("路径不能为空")); // ✅ 错误消息
}

// 模拟处理
Ok(format!("处理文件: {}", path)) // ✅ 格式化结果
}

fn main() {
let config = Config::new();
println!("配置: {} -> {}", config.name, config.path);

match process_file("test.txt") {
Ok(result) => println!("{}", result),
Err(error) => eprintln!("错误: {}", error),
}
}

&str 和 String 的区别

特性&strString
所有权借用/引用拥有所有权
可变性不可变可变(如果声明为 mut)
内存位置栈上(指针+长度)堆上分配
大小固定(16字节:指针8字节+长度8字节)可变
性能更快(无内存分配)较慢(需要堆分配)
fn main() {
// &str - 字符串切片
let str_slice: &str = "Hello"; // 存储在程序二进制中

// String - 拥有所有权的字符串
let owned_string: String = String::from("Hello"); // 堆上分配

// 转换
let from_string: &str = &owned_string; // String -> &str
let to_string: String = str_slice.to_string(); // &str -> String

println!("字符串切片: {}", str_slice);
println!("拥有所有权: {}", owned_string);
println!("转换结果: {} -> {}", from_string, to_string);
}

字符串切片操作

fn main() {
let s = "Hello, Rust!";

// 字符串切片
let hello = &s[0..5]; // "Hello"
let rust = &s[7..11]; // "Rust"
let full = &s[..]; // 整个字符串

println!("原字符串: {}", s);
println!("切片1: {}", hello);
println!("切片2: {}", rust);
println!("完整: {}", full);

// 注意:中文字符串切片需要小心字节边界
let chinese = "你好世界";
let hello_cn = &chinese[0..6]; // "你好" (每个中文字符3字节)
println!("中文切片: {}", hello_cn);
}

字符串方法

fn main() {
let s = " Hello, Rust! ";

// 常用方法
println!("长度: {}", s.len()); // 字节长度
println!("字符数: {}", s.chars().count()); // 字符数量
println!("是否为空: {}", s.is_empty()); // false
println!("包含Rust: {}", s.contains("Rust")); // true
println!("以Hello开头: {}", s.starts_with(" Hello")); // true
println!("去除空格: '{}'", s.trim()); // "Hello, Rust!"

// 分割字符串
let words: Vec<&str> = s.trim().split(", ").collect();
println!("分割结果: {:?}", words); // ["Hello", "Rust!"]

// 替换
let replaced = s.replace("Rust", "World");
println!("替换后: {}", replaced);
}

字符串与函数

// 接受字符串切片的函数(推荐)
fn print_string(s: &str) {
println!("字符串: {}", s);
}

// 接受 String 的函数
fn take_ownership(s: String) {
println!("拥有: {}", s);
// s 在这里被销毁
}

fn main() {
let string_literal = "Hello"; // &str
let owned_string = String::from("World"); // String

// &str 可以直接传递
print_string(string_literal);

// String 需要借用才能传递给接受 &str 的函数
print_string(&owned_string);

// 传递所有权
take_ownership(owned_string);
// owned_string 在这里不再可用

// 如果还想使用,需要克隆
let another_string = String::from("Rust");
take_ownership(another_string.clone());
print_string(&another_string); // 仍然可用
}

&String 类型详解

&String 是对 String 的引用,但在实际使用中很少直接使用,因为它会自动解引用为 &str

fn main() {
let owned_string = String::from("Hello, Rust!");

// &String - 对 String 的引用
let string_ref: &String = &owned_string;

// &String 会自动解引用为 &str
let str_ref: &str = &owned_string; // 等价于 string_ref.as_str()

println!("&String: {}", string_ref);
println!("&str: {}", str_ref);

// 两者在使用上几乎相同
print_message(string_ref); // &String 自动转为 &str
print_message(str_ref); // 直接使用 &str
}

fn print_message(msg: &str) {
println!("消息: {}", msg);
}

三种字符串类型的关系

fn main() {
// String - 拥有所有权的字符串
let owned: String = String::from("Hello");

// &String - 对 String 的引用
let string_ref: &String = &owned;

// &str - 字符串切片引用
let str_ref: &str = &owned; // 或者 string_ref.as_str()

// 自动解引用:&String -> &str
demonstrate_deref(string_ref); // &String 自动转为 &str
demonstrate_deref(str_ref); // 直接使用 &str
}

fn demonstrate_deref(s: &str) {
println!("接收到: {}", s);
}

详细应用场景

1. 函数参数设计
// ✅ 推荐:使用 &str - 最灵活
fn process_text(text: &str) -> usize {
text.len()
}

// ❌ 不推荐:使用 &String - 限制性强
fn process_text_string(text: &String) -> usize {
text.len()
}

// ❌ 不推荐:使用 String - 获取所有权
fn process_text_owned(text: String) -> usize {
text.len()
}

fn main() {
let literal = "hello"; // &str
let owned = String::from("world"); // String
let string_ref = &owned; // &String

// 使用 &str 参数的函数 - 全部可以接受
println!("{}", process_text(literal)); // ✅ &str
println!("{}", process_text(&owned)); // ✅ &String -> &str
println!("{}", process_text(string_ref)); // ✅ &String -> &str

// 使用 &String 参数的函数 - 限制较多
// println!("{}", process_text_string(literal)); // ❌ 不能直接传 &str
println!("{}", process_text_string(&owned)); // ✅ 可以传 &String
println!("{}", process_text_string(string_ref)); // ✅ 可以传 &String

// 使用 String 参数的函数 - 需要转移所有权
println!("{}", process_text_owned(owned.clone())); // ✅ 需要克隆
// owned 在这里仍然可用,因为使用了 clone()
}
2. 数据存储场景
// 配置结构体
struct Config {
app_name: String, // 拥有数据,可以修改
version: &'static str, // 引用静态字符串,不可修改
}

// 临时处理结构体
struct TextProcessor<'a> {
input: &'a str, // 借用外部数据,生命周期受限
buffer: String, // 拥有内部缓冲区,可以修改
}

impl<'a> TextProcessor<'a> {
fn new(input: &'a str) -> Self {
Self {
input,
buffer: String::new(),
}
}

fn process(&mut self) -> &str {
self.buffer = format!("处理后的: {}", self.input.to_uppercase());
&self.buffer
}
}

fn main() {
let config = Config {
app_name: String::from("我的应用"), // 可以在运行时确定
version: "1.0.0", // 编译时确定的常量
};

let input_text = "hello world";
let mut processor = TextProcessor::new(input_text);
let result = processor.process();

println!("应用: {}, 版本: {}", config.app_name, config.version);
println!("处理结果: {}", result);
}
3. 性能优化场景
// 高性能文本处理
fn count_words_efficient(text: &str) -> usize {
// 使用 &str 避免内存分配
text.split_whitespace().count()
}

// 需要修改的场景
fn normalize_text(text: &str) -> String {
// 返回 String 因为需要创建新的修改后的字符串
text.trim()
.to_lowercase()
.replace(" ", " ")
}

// 批量处理场景
fn process_lines(lines: &[&str]) -> Vec<String> {
lines.iter()
.map(|line| normalize_text(line))
.collect()
}

fn main() {
let text = " Hello WORLD ";

// 只读操作 - 使用 &str,无内存分配
let word_count = count_words_efficient(text);
println!("单词数: {}", word_count);

// 修改操作 - 返回 String
let normalized = normalize_text(text);
println!("标准化后: '{}'", normalized);

// 批量处理
let lines = vec![" Line 1 ", "LINE 2", " line 3 "];
let processed = process_lines(&lines);
println!("处理后的行: {:?}", processed);
}
4. API 设计场景
// 文件操作 API
use std::fs;
use std::io::Result;

struct FileManager;

impl FileManager {
// 读取文件 - 返回 String(拥有数据)
fn read_file(path: &str) -> Result<String> {
fs::read_to_string(path)
}

// 写入文件 - 接受 &str(灵活性)
fn write_file(path: &str, content: &str) -> Result<()> {
fs::write(path, content)
}

// 追加内容 - 接受 &str
fn append_to_file(path: &str, content: &str) -> Result<()> {
use std::fs::OpenOptions;
use std::io::Write;

let mut file = OpenOptions::new()
.create(true)
.append(true)
.open(path)?;

writeln!(file, "{}", content)?;
Ok(())
}
}

// 日志系统
struct Logger {
buffer: String,
}

impl Logger {
fn new() -> Self {
Self {
buffer: String::new(),
}
}

// 添加日志 - 接受 &str
fn log(&mut self, message: &str) {
use std::time::SystemTime;

let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();

self.buffer.push_str(&format!("[{}] {}\n", timestamp, message));
}

// 获取日志 - 返回 &str(借用内部数据)
fn get_logs(&self) -> &str {
&self.buffer
}

// 清空日志
fn clear(&mut self) {
self.buffer.clear();
}
}

fn main() -> Result<()> {
// 文件操作示例
let content = "Hello, file!";
FileManager::write_file("test.txt", content)?;

let file_content = FileManager::read_file("test.txt")?;
println!("文件内容: {}", file_content);

FileManager::append_to_file("test.txt", "追加的内容")?;

// 日志系统示例
let mut logger = Logger::new();
logger.log("应用启动");
logger.log("处理用户请求");
logger.log("操作完成");

println!("日志记录:\n{}", logger.get_logs());

Ok(())
}

最佳实践总结

  1. 函数参数优先使用 &str

    • 最灵活,可以接受所有字符串类型
    • 性能最好,无额外内存分配
    • API 设计更友好
  2. 返回值根据需求选择

    • 返回 &str:借用现有数据,生命周期受限
    • 返回 String:创建新数据,调用者拥有所有权
  3. 数据存储根据用途选择

    • String:需要拥有和修改数据
    • &str:只读引用,通常用于静态字符串
  4. 避免使用 &String

    • 几乎总是可以用 &str 替代
    • &String 会自动解引用为 &str
  5. 性能考虑

    • &str 操作最快(无内存分配)
    • String 操作涉及堆分配
    • 避免不必要的 .to_string() 调用

复合类型

元组

fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);

// 解构
let (x, y, z) = tup;
println!("x: {}, y: {}, z: {}", x, y, z);

// 索引访问
let five_hundred = tup.0;
let six_point_four = tup.1;
let one = tup.2;

println!("索引访问: {}, {}, {}", five_hundred, six_point_four, one);
}

数组

fn main() {
let a = [1, 2, 3, 4, 5];
let months = ["January", "February", "March"];

// 指定类型和长度
let a: [i32; 5] = [1, 2, 3, 4, 5];

// 初始化相同值
let a = [3; 5]; // [3, 3, 3, 3, 3]

// 访问元素
let first = a[0];
let second = a[1];

println!("第一个元素: {}", first);
println!("第二个元素: {}", second);
}

类型转换

fn main() {
let x = 10;
let y = x as f64; // 显式类型转换

println!("x: {}, y: {}", x, y);

// 字符串转数字
let guess: u32 = "42".parse().expect("不是一个数字!");
println!("猜测的数字: {}", guess);
}