Add the crate and encode a string
[dependencies]
base64 = "0.22" # the snippets below also build with 0.23
Since 0.21, all work goes through an Engine. The prelude module exports the Engine trait and four ready-made engines:
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 accepts anything that is AsRef<[u8]>, so &str, String, Vec<u8> and byte literals all work. Rust strings are UTF-8, so no conversion step is needed. String::from_utf8 returns an error instead of guessing if the decoded bytes are not valid UTF-8.
The prelude names map to base64::engine::general_purpose:
| Prelude | Engine | Alphabet | Padding on encode | Padding on decode |
|---|---|---|---|---|
BASE64_STANDARD | STANDARD | + / | yes | required |
BASE64_STANDARD_NO_PAD | STANDARD_NO_PAD | + / | no | must be absent |
BASE64_URL_SAFE | URL_SAFE | - _ | yes | required |
BASE64_URL_SAFE_NO_PAD | URL_SAFE_NO_PAD | - _ | no | must be absent |
URL-safe Base64 with and without padding
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"<<???>>"
The no-pad engines reject padded input (BASE64_URL_SAFE_NO_PAD.decode("PDw_Pz8-Pg==") returns Err(InvalidPadding)). If you accept tokens from several producers, build an engine that does not care:
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
The other modes are RequireCanonical (the default for padded engines) and RequireNone. Background on the variants: Base64 vs Base64URL and padding explained.
Encode and decode a file
let data = std::fs::read("hello.txt")?;
println!("{}", BASE64_STANDARD.encode(&data)); // aGVsbG8K
std::fs::write("copy.txt", BASE64_STANDARD.decode("aGVsbG8K")?)?;
To append to an existing buffer without a new allocation, use encode_string:
let mut s = String::from("Basic ");
BASE64_STANDARD.encode_string(b"user:pass", &mut s);
println!("{s}"); // Basic dXNlcjpwYXNz
Streaming with EncoderWriter and DecoderReader
The write and read modules wrap std::io types, so large files never sit in memory:
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)?;
Call finish() explicitly. Dropping the writer also tries to flush, but it cannot report errors. On a 300 KB test file the streamed output equaled BASE64_STANDARD.encode, and the decoded bytes matched the original.
Validation and DecodeError
decode returns Result<Vec<u8>, DecodeError>. These are the results with BASE64_STANDARD, shown as Debug output from 0.22.1 and the Display message:
| Input | Variant | Message |
|---|---|---|
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. |
Byte values are decimal: 95 is _, 61 is =, 10 is a newline. InvalidLastSymbol means non-zero padding bits, which this crate rejects by default; with_decode_allow_trailing_bits(true) relaxes that. In 0.23 the InvalidLastSymbol variant changed and its message now reads Invalid last symbol 0x6c ('l') at offset 2, decoded as 0b00100101. Match on the variant name, not the text.
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}"),
}
Paste a failing string into the Base64 validator to see the offending character; the Base64 errors guide goes through the causes.
Pitfalls
- Old tutorials.
encode_config(x, base64::URL_SAFE)and the config constants are pre-0.21 and no longer compile;base64::encode(x)still builds with a deprecation warning. Translate them toURL_SAFE.encode(x)andSTANDARD.encode(x). - Forgetting the trait import. Without
use base64::Engine;(or the prelude), calling.encodeon an engine fails with “no method namedencodefound”. - Whitespace. The decoder treats newlines and spaces as invalid bytes. Strip them, for example with
s.split_ascii_whitespace().collect::<String>(). - No MIME helper. The crate does not wrap lines. Split the output into 76-character chunks yourself if you produce email bodies.
Version notes
- 0.21: Engine API introduced;
encode/decodefree functions deprecated,*_configfunctions removed. Configs areGeneralPurposeConfigplusDecodePaddingMode. - 0.22:
DecodeError::InvalidLengthnow refers to the number of valid symbols;decode_sliceworks with exactly sized buffers. - 0.23: SIMD engines behind the default
simd-unsafefeature, more preconfigured constants, custom padding symbols, MSRV 1.71. The general-purpose engines used on this page are unchanged.
FAQ
Why is base64::encode marked as deprecated?
Since 0.21 the free functions base64::encode and base64::decode are deprecated wrappers (still present in 0.23). Use an engine instead: use base64::prelude::*; then BASE64_STANDARD.encode(data).
Why does the base64 crate reject input without padding?
STANDARD requires canonical padding. Use URL_SAFE_NO_PAD or STANDARD_NO_PAD for unpadded input, or build an engine with DecodePaddingMode::Indifferent to accept both.
Does the base64 crate skip newlines?
No. "aGVs\nbG8K" fails with InvalidByte(4, 10). Remove whitespace before decoding MIME or PEM bodies.