Conversions between types
Stylus smart contracts often need to convert between different type representations: Rust native types, Alloy primitives, and Solidity types. The Stylus SDK provides comprehensive conversion mechanisms through the AbiType trait and various standard Rust conversion traits.
Understanding type relationships
The Stylus SDK establishes a bidirectional relationship between Rust and Solidity types:
- Alloy provides: Solidity types → Rust types mapping via
SolType - Stylus SDK provides: Rust types → Solidity types mapping via
AbiType
Together, these create a complete two-way type system for interoperability.
Key type mappings
| Rust/Alloy type | Solidity type | Notes |
|---|---|---|
bool | bool | Native boolean |
u8, u16, u32, u64, u128 | uint8 through uint128 | Native unsigned integers |
i8, i16, i32, i64, i128 | int8 through int128 | Native signed integers |
Uint<BITS, LIMBS> | uintBITS | Arbitrary-sized unsigned integers (8-256 bits) |
Signed<BITS, LIMBS> | intBITS | Arbitrary-sized signed integers (8-256 bits) |
U256 | uint256 | 256-bit unsigned integer (most common) |
Address | address | 20-byte Ethereum address |
FixedBytes<N> | bytesN | Fixed-size byte array |
Bytes | bytes | Dynamic byte array |
String | string | Dynamic UTF-8 string |
Vec<T> | T[] | Dynamic array |
[T; N] | T[N] | Fixed-size array |
(T1, T2, ...) | (T1, T2, ...) | Tuple types |
Important: The SDK treats Vec<u8> as Solidity uint8[]. For Solidity bytes, use alloy_primitives::Bytes.
Converting numeric types
Creating integers from literals
use stylus_sdk::alloy_primitives::{U256, I256, U8, I8};
// From integer literals
let small: U8 = U8::from(1);
let large: U256 = U256::from(255);
// For signed integers
let positive: I8 = I8::unchecked_from(127);
let negative: I8 = I8::unchecked_from(-1);
let signed_large: I256 = I256::unchecked_from(0xff_u64);
Parsing from strings
use stylus_sdk::alloy_primitives::I256;
// Parse decimal strings
let a = I256::try_from(20003000).unwrap();
let b = "100".parse::<I256>().unwrap();
// Parse hexadecimal strings
let c = "-0x138f".parse::<I256>().unwrap();
// Underscores are ignored for readability
let d = "1_000_000".parse::<I256>().unwrap();
// Arithmetic works as expected
let result = a * b + c - d;
Integer constants
use stylus_sdk::alloy_primitives::I256;
let max = I256::MAX; // Maximum value
let min = I256::MIN; // Minimum value
let zero = I256::ZERO; // Zero
let minus_one = I256::MINUS_ONE; // -1
Converting between integer sizes
use stylus_sdk::alloy_primitives::{Uint, Signed, U256};
// Between Alloy integer types (same bit-width)
let uint_value = Uint::<128, 2>::from(999);
let u128_value: u128 = uint_value.try_into()
.map_err(|_| "conversion error")
.unwrap();
// Between different bit-widths
let small = Uint::<8, 1>::from(100);
let large = U256::from(small);
The SDK uses the ConvertInt trait internally to enable conversions between Alloy's Uint<BITS, LIMBS> types and Rust native integer types like u8, u16, u32, u64, and u128.
Converting addresses
Creating addresses
use stylus_sdk::alloy_primitives::{Address, address};
// From a 20-byte array
let addr1 = Address::from([0x11; 20]);
// Using the address! macro with checksummed string
let addr2 = address!("d8da6bf26964af9d7eed9e03e53415d37aa96045");
// From a byte slice
let bytes: [u8; 20] = [0xd8, 0xda, 0x6b, 0xf2, /* ... */];
let addr3 = Address::from(bytes);
Converting addresses to bytes
use stylus_sdk::alloy_primitives::Address;
let addr = address!("d8da6bf26964af9d7eed9e03e53415d37aa96045");
// Get reference to underlying bytes
let bytes_ref: &[u8] = addr.as_ref();
// Use in byte concatenation
let data = [addr.as_ref(), other_data].concat();