Tuple Variants
A tuple variant is an enum variant that carries one or more unnamed positional data fields enclosed in parentheses VariantName(T1, T2, ...). Just as a unit variant mirrors a unit-like struct (struct Quit;), a tuple variant mirrors a tuple struct (struct Write(String);) embedded directly inside an enum.
Declaring and Constructing Tuple Variants
Each tuple variant can hold a different number and combination of field types:
#[derive(Debug, PartialEq)]
enum IpAddress {
V4(u8, u8, u8, u8), // 4 positional `u8` fields
V6(String), // 1 positional `String` field
}
fn main() {
let home = IpAddress::V4(127, 0, 0, 1);
let loopback_v6 = IpAddress::V6(String::from("::1"));
println!("IPv4: {home:?}");
println!("IPv6: {loopback_v6:?}");
}
IPv4: V4(127, 0, 0, 1)
IPv6: V6("::1")
Extracting Tuple Variant Fields via Pattern Matching
Unlike a standalone tuple struct (struct Color(u8, u8, u8);)—where you can write color.0 directly—you cannot use dot-index syntax (home.0) on an enum value because the compiler does not know at compile time which variant the enum currently holds!
Instead, you unpack the positional fields of a tuple variant using pattern matching (match, if let, let ... else, or while let):
enum IpAddress {
V4(u8, u8, u8, u8),
V6(String),
}
fn format_ip(ip: &IpAddress) -> String {
match ip {
IpAddress::V4(a, b, c, d) => format!("{a}.{b}.{c}.{d}"),
IpAddress::V6(addr) => addr.clone(),
}
}
fn is_private_ipv4(ip: &IpAddress) -> bool {
match ip {
// Match specific literal values (`10`, `192, 168`) and ignore others with `..`:
IpAddress::V4(10, ..) => true,
IpAddress::V4(192, 168, ..) => true,
IpAddress::V4(172, second, ..) if (16..=31).contains(second) => true,
_ => false,
}
}
fn main() {
let local = IpAddress::V4(192, 168, 1, 50);
let public = IpAddress::V4(8, 8, 8, 8);
println!("{} is private: {}", format_ip(&local), is_private_ipv4(&local));
println!("{} is private: {}", format_ip(&public), is_private_ipv4(&public));
}
192.168.1.50 is private: true
8.8.8.8 is private: false
Standard Library Tuple Variants (Some, Ok, Err)
The most widely used tuple variants in Rust are the 1-element tuple variants of Option<T> and Result<T, E> in the standard library prelude:
pub enum Option<T> {
None, // Unit variant
Some(T), // 1-element Tuple variant!
}
pub enum Result<T, E> {
Ok(T), // 1-element Tuple variant!
Err(E), // 1-element Tuple variant!
}
Tuple Variants Are Also First-Class Constructor Functions
Whenever you define a tuple variant EnumName::Variant(T1, T2), Rust automatically creates a constructor function with the signature fn(T1, T2) -> EnumName.
This allows you to pass Some, Ok, Err, or any custom tuple variant directly as a function pointer to .map(), .and_then(), or higher-order functions without writing a closure |x| EnumName::Variant(x):
#[derive(Debug)]
enum Token {
Number(i32),
Word(String),
}
fn main() {
let raw_numbers = [10, 20, 30];
// Pass `Token::Number` (`fn(i32) -> Token`) directly to `.map()`:
let tokens: [Token; 3] = raw_numbers.map(Token::Number);
// Wrap an iterator of strings into `Token::Word`:
let word_tokens: Vec<Token> = ["let", "fn"]
.into_iter()
.map(String::from)
.map(Token::Word)
.collect();
println!("Number tokens: {tokens:?}");
println!("Word tokens: {word_tokens:?}");
}
Number tokens: [Number(10), Number(20), Number(30)]
Word tokens: [Word("let"), Word("fn")]
##Wrapping a Standalone Struct Inside a 1-Element Tuple Variant
When an enum variant has many fields or needs to be passed around as its own standalone type to helper functions, a common design pattern is to define a separate struct for that variant's data and wrap it inside a 1-element tuple variant (Variant(MyStruct)):
#[derive(Debug)]
struct TlsConfig {
cert_path: String,
port: u16,
}
#[derive(Debug)]
enum Transport {
Plaintext(u16),
Encrypted(TlsConfig), // 1-element tuple variant wrapping `TlsConfig`!
}
// Now helper functions can accept `&TlsConfig` directly as a type!
fn start_tls_server(cfg: &TlsConfig) {
println!("Starting TLS on port {} with cert '{}'", cfg.port, cfg.cert_path);
}
fn main() {
let mode = Transport::Encrypted(TlsConfig {
cert_path: String::from("/etc/ssl/server.pem"),
port: 443,
});
if let Transport::Encrypted(ref tls_cfg) = mode {
start_tls_server(tls_cfg);
}
}
Starting TLS on port 443 with cert '/etc/ssl/server.pem'