添加 crate 并编码字符串
[dependencies]
base64 = "0.22" # the snippets below also build with 0.23
从 0.21 开始,所有操作都通过 Engine 完成。prelude 模块导出了 Engine trait 和四个现成的 engine:
use base64::prelude::*;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let enc = BASE64_STANDARD.encode("héllo wörld");
println!("{enc}"); // aMOpbGxvIHfDtnJsZA==
let bytes = BASE64_STANDARD.decode(&enc)?;
println!("{}", String::from_utf8(bytes)?); // héllo wörld
Ok(())
}
encode 接受任何实现了 AsRef<[u8]> 的类型,所以 &str、String、Vec<u8> 和字节字面量都可以。Rust 字符串本身就是 UTF-8,不需要转换。如果解码出的字节不是有效的 UTF-8,String::from_utf8 会返回错误,而不是去猜测。
prelude 中的名字对应 base64::engine::general_purpose 中的 engine:
| Prelude | Engine | 字母表 | 编码时的填充 | 解码时的填充 |
|---|---|---|---|---|
BASE64_STANDARD | STANDARD | + / | 有 | 必须有 |
BASE64_STANDARD_NO_PAD | STANDARD_NO_PAD | + / | 无 | 必须没有 |
BASE64_URL_SAFE | URL_SAFE | - _ | 有 | 必须有 |
BASE64_URL_SAFE_NO_PAD | URL_SAFE_NO_PAD | - _ | 无 | 必须没有 |
带填充和不带填充的 URL 安全 Base64
let d = b"<<???>>";
BASE64_URL_SAFE.encode(d); // "PDw_Pz8-Pg=="
BASE64_URL_SAFE_NO_PAD.encode(d); // "PDw_Pz8-Pg"
let back = BASE64_URL_SAFE_NO_PAD.decode("PDw_Pz8-Pg")?; // b"<<???>>"
无填充的 engine 会拒绝带填充的输入(BASE64_URL_SAFE_NO_PAD.decode("PDw_Pz8-Pg==") 返回 Err(InvalidPadding))。如果你要接受来自多个生成方的令牌,就构造一个不在乎填充的 engine:
use base64::alphabet;
use base64::engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig};
use base64::engine::DecodePaddingMode;
const URL_LENIENT: GeneralPurpose = GeneralPurpose::new(
&alphabet::URL_SAFE,
GeneralPurposeConfig::new()
.with_encode_padding(false)
.with_decode_padding_mode(DecodePaddingMode::Indifferent),
);
URL_LENIENT.decode("PDw_Pz8-Pg").is_ok(); // true
URL_LENIENT.decode("PDw_Pz8-Pg==").is_ok(); // true
另外两种模式是 RequireCanonical(带填充 engine 的默认值)和 RequireNone。各种变体的背景知识见 Base64 与 Base64URL 的区别 和 填充详解。
编码和解码文件
let data = std::fs::read("hello.txt")?;
println!("{}", BASE64_STANDARD.encode(&data)); // aGVsbG8K
std::fs::write("copy.txt", BASE64_STANDARD.decode("aGVsbG8K")?)?;
要追加到已有的缓冲区而不重新分配内存,请使用 encode_string:
let mut s = String::from("Basic ");
BASE64_STANDARD.encode_string(b"user:pass", &mut s);
println!("{s}"); // Basic dXNlcjpwYXNz
用 EncoderWriter 和 DecoderReader 流式处理
write 和 read 模块包装了 std::io 类型,所以大文件永远不必整个放进内存:
use std::fs::File;
use std::io::{self, Read};
let mut input = File::open("big.bin")?;
let mut w = base64::write::EncoderWriter::new(File::create("big.b64")?, &BASE64_STANDARD);
io::copy(&mut input, &mut w)?;
w.finish()?; // writes the final block and padding
let mut r = base64::read::DecoderReader::new(File::open("big.b64")?, &BASE64_STANDARD);
let mut out = Vec::new();
r.read_to_end(&mut out)?;
请显式调用 finish()。drop 写入器时虽然也会尝试刷新,但无法报告错误。在一个 300 KB 的测试文件上,流式输出与 BASE64_STANDARD.encode 相同,解码出的字节也与原文件一致。
校验与 DecodeError
decode 返回 Result<Vec<u8>, DecodeError>。下面是使用 BASE64_STANDARD 时的结果,列出了 0.22.1 的 Debug 输出和 Display 信息:
| 输入 | 变体 | 错误信息 |
|---|---|---|
PDw_Pz8-Pg== | InvalidByte(3, 95) | Invalid symbol 95, offset 3. |
aGk | InvalidPadding | Invalid padding |
aGk=!! | InvalidByte(3, 61) | Invalid symbol 61, offset 3. |
a | InvalidLength(1) | Invalid input length: 1 |
aGl= | InvalidLastSymbol(2, 108) | Invalid last symbol 108, offset 2. |
"aGVs\nbG8K" | InvalidByte(4, 10) | Invalid symbol 10, offset 4. |
字节值是十进制的:95 是 _,61 是 =,10 是换行符。InvalidLastSymbol 表示填充位不为零,这个 crate 默认会拒绝;with_decode_allow_trailing_bits(true) 可以放宽这一限制。在 0.23 中,InvalidLastSymbol 变体有所改变,错误信息变成了 Invalid last symbol 0x6c ('l') at offset 2, decoded as 0b00100101.。请按变体名称匹配,而不是按文本匹配。
use base64::DecodeError;
match BASE64_STANDARD.decode(input) {
Ok(bytes) => { /* use bytes */ }
Err(DecodeError::InvalidByte(offset, byte)) => eprintln!("bad byte {byte} at {offset}"),
Err(e) => eprintln!("invalid Base64: {e}"),
}
把解码失败的字符串粘贴到 Base64 校验工具 中,就能看到出问题的字符;Base64 错误指南 逐一分析了各种原因。
常见陷阱
- 过时的教程:
encode_config(x, base64::URL_SAFE)和那些 config 常量都是 0.21 之前的写法,现在已经无法编译;base64::encode(x)仍然能构建,但会给出废弃警告。请把它们分别改写为URL_SAFE.encode(x)和STANDARD.encode(x)。 - 忘了导入 trait:没有
use base64::Engine;(或 prelude)时,对 engine 调用.encode会报 “no method namedencodefound” 错误。 - 空白字符:解码器把换行符和空格视为无效字节。请先去掉它们,例如用
s.split_ascii_whitespace().collect::<String>()。 - 没有 MIME 辅助函数:这个 crate 不会换行。如果要生成邮件正文,请自己把输出切成 76 个字符一行。
版本说明
- 0.21:引入 Engine API;自由函数
encode/decode被废弃,*_config函数被移除。配置改用GeneralPurposeConfig加DecodePaddingMode。 - 0.22:
DecodeError::InvalidLength现在指有效符号的数量;decode_slice可以配合大小恰好的缓冲区使用。 - 0.23:默认的
simd-unsafefeature 下提供 SIMD engine,新增更多预配置常量、自定义填充符号,MSRV 为 1.71。本页使用的通用 engine 没有变化。
常见问题
为什么 base64::encode 被标记为已废弃?
从 0.21 起,自由函数 base64::encode 和 base64::decode 就成了已废弃的包装函数(0.23 中仍然存在)。请改用 engine:先 use base64::prelude::*;,再调用 BASE64_STANDARD.encode(data)。
为什么 base64 crate 拒绝不带填充的输入?
STANDARD 要求规范的填充。对于不带填充的输入,请使用 URL_SAFE_NO_PAD 或 STANDARD_NO_PAD,或者用 DecodePaddingMode::Indifferent 构造一个两种都接受的 engine。
base64 crate 会跳过换行符吗?
不会。"aGVs\nbG8K" 会以 InvalidByte(4, 10) 失败。解码 MIME 或 PEM 正文之前,请先去掉空白。