Interface compatibility with wrappers
The Adapter pattern converts the interface of a class into another interface that clients expect. It lets classes work together that couldn't otherwise because of incompatible interfaces. In Rust, we implement adapters using wrapper types and trait implementations.
Adapters are needed when:
use std::io::{Read, Write, Result as IoResult};
/// Legacy temperature sensor (third-party code we can't modify)
pub struct LegacyThermometer {
reading_fahrenheit: f64,
}
impl LegacyThermometer {
pub fn new() -> Self {
LegacyThermometer { reading_fahrenheit: 72.0 }
}
pub fn get_temperature_f(&self) -> f64 {
self.reading_fahrenheit
}
pub fn set_temperature_f(&mut self, temp: f64) {
self.reading_fahrenheit = temp;
}
}
impl Default for LegacyThermometer {
fn default() -> Self {
Self::new()
}
}
/// Modern temperature interface (what our code expects)
pub trait TemperatureSensor {
fn read_celsius(&self) -> f64;
fn read_fahrenheit(&self) -> f64;
fn read_kelvin(&self) -> f64;
}
/// Adapter that wraps LegacyThermometer
pub struct ThermometerAdapter {
legacy: LegacyThermometer,
}
impl ThermometerAdapter {
pub fn new(legacy: LegacyThermometer) -> Self {
ThermometerAdapter { legacy }
}
}
impl TemperatureSensor for ThermometerAdapter {
fn read_fahrenheit(&self) -> f64 {
self.legacy.get_temperature_f()
}
fn read_celsius(&self) -> f64 {
(self.legacy.get_temperature_f() - 32.0) * 5.0 / 9.0
}
fn read_kelvin(&self) -> f64 {
self.read_celsius() + 273.15
}
}
/// Adapter using the newtype pattern
pub struct CelsiusSensor(pub f64);
impl TemperatureSensor for CelsiusSensor {
fn read_celsius(&self) -> f64 {
self.0
}
fn read_fahrenheit(&self) -> f64 {
self.0 * 9.0 / 5.0 + 32.0
}
fn read_kelvin(&self) -> f64 {
self.0 + 273.15
}
}
/// Adapting between different I/O traits
pub struct StringWriter {
buffer: String,
}
impl StringWriter {
pub fn new() -> Self {
StringWriter { buffer: String::new() }
}
pub fn into_string(self) -> String {
self.buffer
}
}
impl Default for StringWriter {
fn default() -> Self {
Self::new()
}
}
impl Write for StringWriter {
fn write(&mut self, buf: &[u8]) -> IoResult<usize> {
let s = std::str::from_utf8(buf)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
self.buffer.push_str(s);
Ok(buf.len())
}
fn flush(&mut self) -> IoResult<()> {
Ok(())
}
}
/// Adapter for external logging library
pub trait Logger {
fn log(&self, level: LogLevel, message: &str);
fn debug(&self, message: &str) {
self.log(LogLevel::Debug, message);
}
fn info(&self, message: &str) {
self.log(LogLevel::Info, message);
}
fn warn(&self, message: &str) {
self.log(LogLevel::Warn, message);
}
fn error(&self, message: &str) {
self.log(LogLevel::Error, message);
}
}
#[derive(Debug, Clone, Copy)]
pub enum LogLevel {
Debug,
Info,
Warn,
Error,
}
/// External library's logger (can't modify)
pub struct ExternalLogger;
impl ExternalLogger {
pub fn write_log(&self, level: u8, msg: &str) {
println!("[External L{}] {}", level, msg);
}
}
/// Adapter for the external logger
pub struct ExternalLoggerAdapter {
external: ExternalLogger,
}
impl ExternalLoggerAdapter {
pub fn new(external: ExternalLogger) -> Self {
ExternalLoggerAdapter { external }
}
}
impl Logger for ExternalLoggerAdapter {
fn log(&self, level: LogLevel, message: &str) {
let level_code = match level {
LogLevel::Debug => 0,
LogLevel::Info => 1,
LogLevel::Warn => 2,
LogLevel::Error => 3,
};
self.external.write_log(level_code, message);
}
}
/// Two-way adapter (bidirectional)
pub trait JsonSerializer {
fn to_json(&self) -> String;
}
pub trait XmlSerializer {
fn to_xml(&self) -> String;
}
/// Data that implements both through adapters
#[derive(Debug)]
pub struct UserData {
pub name: String,
pub email: String,
}
impl JsonSerializer for UserData {
fn to_json(&self) -> String {
format!(r#"{{"name":"{}","email":"{}"}}"#, self.name, self.email)
}
}
/// Adapter: JSON to XML
pub struct JsonToXmlAdapter<'a, T: JsonSerializer> {
inner: &'a T,
}
impl<'a, T: JsonSerializer> JsonToXmlAdapter<'a, T> {
pub fn new(inner: &'a T) -> Self {
JsonToXmlAdapter { inner }
}
}
impl<T: JsonSerializer> XmlSerializer for JsonToXmlAdapter<'_, T> {
fn to_xml(&self) -> String {
// Simplified: in reality you'd parse the JSON properly
let json = self.inner.to_json();
// Very naive conversion for demonstration
json.replace('{', "<data>")
.replace('}', "</data>")
.replace(':', ">")
.replace(',', "</")
.replace('"', "")
}
}
/// Iterator adapter example
pub struct ChunkIterator<I> {
inner: I,
chunk_size: usize,
}
impl<I> ChunkIterator<I> {
pub fn new(inner: I, chunk_size: usize) -> Self {
ChunkIterator { inner, chunk_size }
}
}
impl<I: Iterator> Iterator for ChunkIterator<I> {
type Item = Vec<I::Item>;
fn next(&mut self) -> Option<Self::Item> {
let chunk: Vec<_> = self.inner.by_ref().take(self.chunk_size).collect();
if chunk.is_empty() {
None
} else {
Some(chunk)
}
}
}
/// Extension trait for easy adapter creation
pub trait IteratorExt: Iterator + Sized {
fn chunks(self, size: usize) -> ChunkIterator<Self> {
ChunkIterator::new(self, size)
}
}
impl<I: Iterator> IteratorExt for I {}
fn main() {
// Legacy adapter
let legacy = LegacyThermometer::new();
let sensor = ThermometerAdapter::new(legacy);
println!("Temperature readings:");
println!(" Fahrenheit: {:.1}°F", sensor.read_fahrenheit());
println!(" Celsius: {:.1}°C", sensor.read_celsius());
println!(" Kelvin: {:.1}K", sensor.read_kelvin());
// Newtype adapter
let celsius = CelsiusSensor(25.0);
println!("\nNewtype sensor:");
println!(" 25°C = {:.1}°F", celsius.read_fahrenheit());
// I/O adapter
let mut writer = StringWriter::new();
writeln!(writer, "Hello, {}", "world").unwrap();
writeln!(writer, "Line 2").unwrap();
println!("\nStringWriter result:\n{}", writer.into_string());
// Logger adapter
let external = ExternalLogger;
let logger = ExternalLoggerAdapter::new(external);
logger.info("Application started");
logger.error("Something went wrong");
// JSON to XML adapter
let user = UserData {
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
};
println!("\nUser as JSON: {}", user.to_json());
let xml_adapter = JsonToXmlAdapter::new(&user);
println!("User as XML: {}", xml_adapter.to_xml());
// Iterator adapter
let numbers: Vec<i32> = (1..=10).collect();
println!("\nChunked iterator:");
for chunk in numbers.iter().copied().chunks(3) {
println!(" {:?}", chunk);
}
}
| Variant | Rust Implementation | Use Case |
|---------|---------------------|----------|
| Object Adapter | Struct wrapping adaptee | Most common, flexible |
| Newtype Adapter | struct Wrapper(Inner) | Zero-cost, simple conversion |
| Blanket Impl | impl Trait for T where T: OtherTrait | When relationship is universal |
| Extension Trait | trait Ext: Sized { fn adapt(self) } | Adding methods to foreign types |
// DON'T: Adapter that exposes internal implementation
impl MyAdapter {
pub fn get_inner(&mut self) -> &mut LegacyType {
&mut self.inner // Breaks encapsulation
}
}
// DON'T: Adapter with too much logic
impl Adapter {
fn process(&self, data: &[u8]) -> Vec<u8> {
// 100 lines of business logic...
// This should be in a separate service!
}
}
// DO: Thin adapter that only converts interfaces
impl Adapter {
fn process(&self, data: &[u8]) -> Vec<u8> {
self.inner.legacy_process(data)
}
}
Read/Write and different sourcesVec implement a stack traitRun this code in the official Rust Playground