Expand description
§Dissolve Derive
A procedural macro for safely taking ownership of inner fields from a struct without exposing those fields publicly.
§Motivation
The dissolve-derive proc macro solves a specific problem: when you have a struct with
private fields and need to transfer ownership of those fields to another part of your code,
you often face two undesirable choices:
- Make fields public: This exposes your internal state and allows arbitrary mutation, breaking encapsulation.
- Write accessor methods: This requires boilerplate code and may involve cloning data, which is inefficient for large structures.
The Dissolve derive macro provides a dissolve(self) method that consumes the struct and
returns its fields in a type-safe manner. This approach:
- Preserves encapsulation: Fields remain private in the original struct
- Enables efficient ownership transfer: No cloning required, fields are moved
- Prevents misuse: The dissolved struct is a different type, preventing it from being used where the original struct is expected
- Provides flexibility: Control which fields are exposed and rename them if needed
- Allows custom visibility: Configure the visibility of the
dissolvemethod itself
§Use Cases
§1. API Boundaries
When building a library, you want to keep internal structure private but allow consumers to extract owned data when they’re done with the struct’s instance:
use dissolve_derive::Dissolve;
#[derive(Dissolve)]
pub struct Connection {
// Private: users can't modify the socket directly
socket: std::net::TcpStream,
// Private: internal state
buffer: Vec<u8>,
// Skip: purely internal, never exposed
#[dissolved(skip)]
statistics: ConnectionStats,
}
// Users can dissolve the connection to reclaim the socket
// without having public access to it during normal operation
let ConnectionDissolved { socket, buffer } = conn.dissolve();§2. Builder Pattern Finalization
Use dissolve to finalize a builder and extract components with controlled visibility:
use dissolve_derive::Dissolve;
#[derive(Dissolve)]
#[dissolve(visibility = "pub(crate)")]
pub struct ConfigBuilder {
database_url: String,
max_connections: u32,
#[dissolved(skip)]
validated: bool,
}
impl ConfigBuilder {
pub fn build(mut self) -> Config {
self.validated = true;
// Only accessible within the crate due to pub(crate)
let ConfigBuilderDissolved { database_url, max_connections } = self.dissolve();
Config { database_url, max_connections }
}
}§3. State Machine Transitions
Safely transition between states by dissolving one state struct and constructing the next:
use std::time::Instant;
use dissolve_derive::Dissolve;
#[derive(Dissolve)]
struct PendingRequest {
request_id: u64,
payload: Vec<u8>,
#[dissolved(skip)]
timestamp: Instant,
}
#[derive(Dissolve)]
struct ProcessedRequest {
request_id: u64,
response: Vec<u8>,
}
impl PendingRequest {
fn process(self) -> ProcessedRequest {
let PendingRequestDissolved { request_id, payload } = self.dissolve();
let response = process_payload(payload);
ProcessedRequest { request_id, response }
}
}
§4. Zero-Cost Abstraction Unwrapping
When wrapping types for compile-time guarantees, use dissolve for efficient unwrapping:
use dissolve_derive::Dissolve;
#[derive(Dissolve)]
pub struct Validated<T> {
inner: T,
#[dissolved(skip)]
validation_token: ValidationToken,
}
impl<T> Validated<T> {
pub fn into_inner(self) -> T {
self.dissolve().inner
}
}
§Attributes
§Container Attributes (on structs)
#[dissolve(visibility = "...")]- Set the visibility of thedissolvemethod- Supported values:
"pub","pub(crate)","pub(super)","pub(self)", or empty string for private - Default:
"pub"if not specified
- Supported values:
§Field Attributes
#[dissolved(skip)]- Skip this field in the dissolved output#[dissolved(rename = "new_name")]- Rename this field in the dissolved struct (named structs only)
§Examples
§Basic Usage
use dissolve_derive::Dissolve;
#[derive(Dissolve)]
struct User {
name: String,
email: String,
}
let user = User {
name: "alice".to_string(),
email: "alice@example.com".to_string(),
};
let UserDissolved { name, email } = user.dissolve();
assert_eq!(name, "alice");§With Custom Visibility
use dissolve_derive::Dissolve;
#[derive(Dissolve)]
#[dissolve(visibility = "pub(crate)")]
pub struct InternalData {
value: i32,
}
// The dissolve method is only accessible within the same crate§Skipping Fields
use dissolve_derive::Dissolve;
#[derive(Dissolve)]
struct Credentials {
username: String,
#[dissolved(skip)]
password: String, // Never exposed, even through dissolve
}§Renaming Fields
use dissolve_derive::Dissolve;
#[derive(Dissolve)]
struct ApiResponse {
#[dissolved(rename = "user_id")]
id: u64,
#[dissolved(rename = "user_name")]
name: String,
}§Tuple Structs
use dissolve_derive::Dissolve;
#[derive(Dissolve)]
struct Coordinate(f64, f64, #[dissolved(skip)] String);
let coord = Coordinate(1.0, 2.0, "label".to_string());
let (x, y) = coord.dissolve();Derive Macros§
- Dissolve
- Derive macro that generates a
dissolve(self)method for structs.