| Crates.io | derive-into |
| lib.rs | derive-into |
| version | 0.2.2 |
| created_at | 2025-04-30 14:22:36.283684+00 |
| updated_at | 2025-06-19 08:23:38.951143+00 |
| description | A Rust derive macro for easily creating conversions between structs and enums |
| homepage | |
| repository | https://github.com/sharonex/derive-into |
| max_upload_size | |
| id | 1654985 |
| size | 101,976 |
A Rust derive macro for easily creating conversions between structs and enums.
From/Into implementationsOption and Vec typesFrom/Into) and fallible (TryFrom) conversionswith_func attributeAdd to your Cargo.toml:
[dependencies]
derive-into = "0.1.0"
use derive_into::Convert;
// Source struct with conversion attributes
#[derive(Convert)]
#[convert(into(path = "Destination"))] // Generate Into<Destination> implementation
struct Source {
id: u32,
#[convert(rename = "full_name")] // Field will be mapped to "full_name" in target
name: String,
}
// Destination struct
struct Destination {
id: u32,
full_name: String,
}
// Usage
let source = Source {
id: 1,
name: "Example".to_string(),
};
let destination: Destination = source.into();
Struct-level attributes can be applied at the struct or enum level to control conversion behavior:
| Attribute | Description |
|---|---|
#[convert(into(path = "Type"))] |
Generate an From<Self> for Type implementation |
#[convert(try_into(path = "Type"))] |
Generate an TryFrom<Self> for Type implementation |
#[convert(try_from(path = "Type"))] |
Generate a TryFrom<Type> for Self implementation |
#[convert(from(path = "Type"))] |
Generate a From<Type> for Self implementation |
#[convert(into(path = "Type", default))] |
Enable default values for fields not explicitly mapped in the target type |
Multiple conversion types can be specified for a single struct:
#[derive(Convert)]
#[convert(into(path = "TargetType"))]
#[convert(try_from(path = "TargetType"))]
struct MyStruct {
// fields
}
| #[convert(try_from(path = "Type"))] | Specify a path for try_from conversion |
Field-level attributes can be applied at three different scopes:
Global scope - applies to all conversion types:
#[convert(rename = "new_name", skip)]
Conversion type scope - applies only to a specific conversion type (into, from, try_from):
#[convert(try_from(skip, default))]
Specific conversion scope - applies only to a singular conversion target:
#[convert(try_from(path = "ApiProduct", skip, default))]
Common field-level attributes:
| Attribute | Description |
|---|---|
#[convert(rename = "new_name")] |
Map this field to a differently named field in the target type |
#[convert(unwrap_or_default)] |
Automatically calls unwrap_or_default on Option value before converting it |
#[convert(unwrap)] |
Automatically unwrap an Option value (fails in try_from if None) |
#[convert(skip)] |
Skip this field during conversion (target must provide a default) |
#[convert(default)] |
Use default value for this field during conversion |
#[convert(with_func = func_name)] |
Use custom function for conversion. The function needs to take a reference to the parent struct |
The macro supports enum-to-enum conversion with similar attribute control:
#[derive(Convert)]
#[convert(into(path = "TargetEnum"))]
enum SourceEnum {
Variant1(u32),
#[convert(rename = "RenamedVariant")]
Variant2 {
value: String,
#[convert(rename = "renamed_field")]
field: u8,
},
Unit,
}
enum TargetEnum {
Variant1(u32),
RenamedVariant {
value: String,
renamed_field: u32,
},
Unit,
}
The macro intelligently handles various type scenarios:
From/Into are automatically convertedOption<T> and Vec<T> with inner type conversionuse derive_into::Convert;
#[derive(Convert)]
#[convert(into(path = "Target"))]
struct Source {
id: u32,
name: String,
}
struct Target {
id: u32,
name: String,
}
// Usage
let source = Source { id: 1, name: "Example".to_string() };
let target: Target = source.into();
The macro automatically handles conversion of inner types for Option and Vec:
use derive_into::Convert;
#[derive(Debug, PartialEq, Default)]
struct Number(u8);
impl From<u8> for Number {
fn from(n: u8) -> Number {
Number(n)
}
}
#[derive(Convert)]
#[convert(into = "Target")]
struct Source {
// Option's inner type will be converted
opt_value: Option<u8>,
// Vec's inner type will be converted
vec_values: Vec<u8>,
}
struct Target {
opt_value: Option<Number>,
vec_values: Vec<Number>,
}
use derive_into::Convert;
#[derive(Convert)]
#[convert(try_from(path = "Source"))]
struct Target {
#[convert(unwrap_or_default)]
value: u32,
}
struct Source {
value: Option<u32>,
}
// This will succeed
let source = Source { value: None };
let target: Result<Target, _> = Target::try_from(source);
assert!(target.is_ok());
// This will fail because value is None
let source = Source { value: None };
let target: Result<Target, _> = Target::try_from(source);
assert!(target.is_err());
use derive_into::Convert;
#[derive(Convert)]
#[convert(try_from(path = "Source"))]
struct Target {
#[convert(unwrap)]
value: u32,
}
struct Source {
value: Option<u32>,
}
// This will succeed
let source = Source { value: Some(42) };
let target: Result<Target, _> = Target::try_from(source);
assert!(target.is_ok());
// This will fail because value is None
let source = Source { value: None };
let target: Result<Target, _> = Target::try_from(source);
assert!(target.is_err());
use derive_into::Convert;
#[derive(Convert)]
#[convert(into(path = "Target", default))]
struct Source {
id: u32,
// No 'extra' field - will use default
}
#[derive(Default)]
struct Target {
id: u32,
extra: String, // Will use Default::default()
}
use derive_into::Convert;
#[derive(Convert)]
#[convert(into(path = "Target"))]
struct Source(Option<u8>, u8);
struct Target(Option<Number>, Number);
use derive_into::Convert;
use std::collections::HashMap;
#[derive(Convert)]
#[convert(into(path = "ApiProduct", default))]
#[convert(try_from(path = "ApiProduct"))]
struct Product {
id: String,
name: NonEmptyString,
// Vector of complex types with renamed field
#[convert(rename = "variants")]
product_variants: Vec<ProductVariant>,
// HashMap with key/value type conversion
#[convert(rename = "price_by_region")]
regional_prices: HashMap<String, f64>,
// Nested struct with its own conversion
manufacturer: Manufacturer,
// Field that will be skipped only during into conversion
#[convert(into(skip))]
internal_tracking_code: String,
// Field that uses default value only during try_from conversion
#[convert(try_from(default))]
sku: String,
// Field that uses custom conversion function
#[convert(try_from(with_func = conversion_func))]
product_err: ProductError,
}
// Custom conversion function
fn conversion_func(val: &ApiProduct) -> ProductError {
ProductError {
message: if val.name.is_empty() {
"Name cannot be empty".to_string()
} else {
"Valid name".to_string()
},
}
}
use derive_into::Convert;
#[derive(Convert)]
#[convert(into(path = "TargetEvent"))]
#[convert(try_from(path = "TargetEvent"))]
enum SourceEvent {
// Tuple variant with type conversion
Click(u64),
// Variant with renamed variant name
#[convert(rename = "LogoutEvent")]
Logout {
username: String,
timestamp: u64,
},
// Variant with nested enum conversion
UserAction {
user_id: u64,
action_type: SourceActionType,
},
}
enum TargetEvent {
// Type conversion in tuple variant
Click(CustomId),
// Renamed variant
LogoutEvent {
username: String,
timestamp: CustomId,
},
// Nested enum conversion
UserAction {
user_id: CustomId,
action_type: TargetActionType,
},
}
use derive_into::Convert;
#[derive(Convert)]
#[convert(try_from(path = "ApiProduct"))]
struct Product {
// Field that requires custom conversion
#[convert(try_from(with_func = validation_function))]
validated_field: SomeType,
}
// Custom conversion function
fn validation_function(source: &ApiProduct) -> SomeType {
// Custom conversion/validation logic
SomeType::new(source.some_field.clone())
}
This project is licensed under the MIT License - see the LICENSE file for details.