数据类型(Data Types)
Rust 是静态类型(Static Typed)语言,这意味着在编译时就必须知道所有变量的类型。
标量类型
整型
长度 | 有符号 | 无符号 |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
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>());
}
字符类型的特点
- Unicode 支持:
char
类型可以表示任何 Unicode 字符 - 固定大小:每个
char
占用 4 字节(32 位) - 单引号:字符字面量使用单引号
'
,字符串使用双引号"
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);
}
使用习惯和最佳实践
社区习惯统计(基于常见开源项目):
-
String::from() - 约 60% 的使用场景
let name = String::from("Alice");
let path = String::from("/home/user"); -
.to_string() - 约 25% 的使用场景
let number_str = 42.to_string();
let converted = some_str_ref.to_string(); -
format!() - 约 10% 的使用场景
let message = format!("Error: {}", error_msg);
-
其他方式 - 约 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 的区别
特性 | &str | String |
---|---|---|
所有权 | 借用/引用 | 拥有所有权 |
可变性 | 不可变 | 可变(如果声明为 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(())
}
最佳实践总结
-
函数参数优先使用
&str
:- 最灵活,可以接受所有字符串类型
- 性能最好,无额外内存分配
- API 设计更友好
-
返回值根据需求选择:
- 返回
&str
:借用现有数据,生命周期受限 - 返回
String
:创建新数据,调用者拥有所有权
- 返回
-
数据存储根据用途选择:
String
:需要拥有和修改数据&str
:只读引用,通常用于静态字符串
-
避免使用
&String
:- 几乎总是可以用
&str
替代 &String
会自动解引用为&str
- 几乎总是可以用
-
性能考虑:
&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);
}