What This Solves
When you write encrypted instructions in Arcium, the results come back as structured data. Previously, developers had to manually parse raw bytes - tracking offsets, sizes, and converting back to the right types. This was error-prone and tedious. Arcium’s type generation system analyzes your circuit’s return type and automatically creates typed Rust structs. This means you can work directly with structured data instead of byte arrays.Mental Model: From Functions to Structs
Here’s the transformation that happens automatically:What You’ll Learn
After reading this guide, you’ll know how to:- Work with automatically generated Rust structs for encrypted computation outputs
- Predict what struct names and fields Arcium will create
- Handle different encryption types (Shared vs MXE) in callbacks
- Debug type generation issues when they arise
30-Second Quick Start
- 
Write your circuit:
- 
Generate types: arcium build
- 
Use in callback:
Your First Generated Type: Simple Addition
Here’s a concrete example. Consider this encrypted instruction that adds two numbers:Enc<Shared, u16>, it automatically generates this output struct:
- Name: add_togetherbecomesAddTogetherOutput
- Field: Always field_0for single return values
- Type: SharedEncryptedStruct<1>because it’s shared-encrypted with 1 value (the u16)
How Type Generation Works
Working with encrypted computation results used to require manual byte parsing - tracking offsets, sizes, and types yourself. Arcium generates typed structs automatically, so you can focus on application logic instead of low-level data handling.The Macro Magic
Here’s the key insight: when you write an encrypted instruction with the#[instruction] macro, something important happens behind the scenes. The macro doesn’t just process your function - it also generates corresponding Rust structs based on your return type.
Enc<Shared, u16> return type and automatically generates:
AddTogetherOutput in your callback even though you never explicitly defined it. The macro created it for you during compilation, and it’s automatically available in your program’s scope.
Immediate Availability
These generated types become available as soon as the macro runs - which happens during normal Rust compilation. You don’t need to wait for a separate build step to start using them in your callback functions.Behind the Scenes: What the Macro Actually Does
Understanding how the#[instruction] macro generates types helps explain why the system works the way it does.
Macro Expansion Process
When Rust processes your#[instruction] macro, here’s what happens:
- Parse the return type: The macro examines your function signature and extracts the return type
- Analyze the structure: It breaks down complex types (tuples, structs, encryption wrappers) into components
- Generate struct definitions: It creates typed structs that match your return type’s structure
- Inject into scope: The generated types become available in your program module automatically
What Gets Generated
For different return types, the macro generates different struct patterns:Why This Approach Works
This macro-driven approach provides several benefits:- Type safety: You get compile-time type checking for encrypted results
- No manual definition: You don’t need to define output structs yourself
- Consistency: All generated types follow the same predictable patterns
- Automatic updates: If you change your function’s return type, the structs update automatically
Understanding LEN Parameters
In ouradd_together example, you saw SharedEncryptedStruct<1>. The <LEN> number tells you how many encrypted scalar values are stored inside.
The <LEN> number represents the count of individual encrypted scalar values:
| Return Type | LEN Value | Why | 
|---|---|---|
| Enc<Shared, u32> | 1 | Single scalar | 
| Enc<Shared, (u32, bool)> | 2 | Two scalars | 
| Enc<Shared, [u32; 5]> | 5 | Five array elements | 
| Enc<Shared, MyStruct> | field count | Count all scalar fields in struct | 
Type Availability and Scope
Where Generated Types Live
Generated types are automatically scoped to your program module and become available immediately after the#[instruction] macro runs. This means:
No Import Required
Unlike external types, you don’t need to import generated types. They’re injected directly into your module’s namespace during macro expansion:Generated Struct Properties
All generated structs automatically receive standard derives that make them work with Anchor:Multiple Instructions, Multiple Types
Each#[instruction] creates its own set of output types:
Generation Process
When you define an encrypted instruction:- Arcium reads your circuit’s output types
- It generates corresponding Rust structs with predictable names
- It automatically detects encryption patterns and creates specialized types
- Everything gets integrated into your #[arcium_callback]functions
How the Naming Works
The naming follows predictable patterns:Your Circuit Gets an Output Struct
If your encrypted instruction is calledadd_together, you get a struct called AddTogetherOutput. Arcium converts your circuit name to PascalCase and adds “Output” at the end.
Fields Are Numbered
Since Anchor doesn’t support tuple structs (yet), Arcium uses numbered fields instead. So if your function returns multiple values, you’ll getfield_0, field_1, field_2, and so on. Not the prettiest names, but they’re consistent and predictable.
Complex Types Get Their Own Structs
When your function returns complex nested data (like tuples or custom structs), Arcium generates additional helper structs with a unified naming convention:- All output structs use {CircuitName}OutputStruct{index}pattern
- Nested structs within outputs use {ParentName}OutputStruct{parent_index}{field_index}pattern
- The naming ensures uniqueness while maintaining consistency
Encryption Types: Shared vs MXE
Arcium automatically detects different encryption patterns and generates the right struct type. Understanding when each type is used helps you predict the generated structs.SharedEncryptedStruct<N>
When your circuit returnsEnc<Shared, T>, Arcium knows this is data that both the client and the MXE can decrypt. It generates a struct that includes everything needed for decryption:
<N> part tells you how many encrypted values are packed inside. So SharedEncryptedStruct<1> has one encrypted value, SharedEncryptedStruct<3> has three, and so on.
In your callback, you can access everything you need:
MXEEncryptedStruct<N>
ForEnc<Mxe, T> data, only the MXE cluster can decrypt it - clients can’t. Since there’s no shared secret needed, the struct is simpler:
encryption_key field here - that’s because clients don’t get to decrypt MXE data.
EncDataStruct<N>
For simple encrypted data without key exchange metadata (less commonly used):EncDataStruct<N> is used in special cases where only ciphertext data is needed without additional metadata. Most applications use SharedEncryptedStruct<N> or MXEEncryptedStruct<N> instead.
Moving to Real-World Applications
Now that you understand the basics with our simple addition example, here’s how this works in real applications. The key difference is that real apps often:- Return multiple values: Functions return tuples or complex structs instead of single values
- Mix encryption types: Some data for users (Shared), some for MXE only (Mxe)
- Handle complex data: Custom structs with multiple fields instead of simple numbers
Real-World Examples
Here’s how this type generation works in actual Arcium applications:Simple Tuple Example
Let’s start with something in between - a function that returns two related values:(Enc<Shared, u32>, Enc<Shared, u32>), Arcium generates:
field_0, and its elements become field_0, field_1, etc.
Voting Application
Now let’s look at a more realistic example. The confidential voting example shows a perfect use case. You have poll data that only the MXE should see, and a user’s vote that should be shared between the user and the MXE:(Enc<Mxe, PollData>, Enc<Shared, bool>), Arcium generates:
Coinflip Application: Back to Basics
After seeing complex tuples and mixed encryption types, let’s look at the simplest possible case. The coinflip example returns just a single encrypted boolean:Enc<Shared, bool> and creates:
Blackjack Application
From the blackjack example with complex game state:Complex Nested Structures
For more complex outputs with nested data structures:ComplexExampleOutputStruct00 and ComplexExampleOutputStruct02, but no ComplexExampleOutputStruct01. This is because:
- field_0(UserData) needs a custom struct →- ComplexExampleOutputStruct00
- field_1(SharedEncryptedStruct) uses a predefined type → no custom struct needed
- field_2((u64, f32) tuple) needs a custom struct →- ComplexExampleOutputStruct02
- field_3(MXEEncryptedStruct) uses a predefined type → no custom struct needed
Working with Generated Types
Pattern Matching
Use destructuring to access nested data:Error Handling
Always handle computation failures:Best Practices
1. Use Descriptive Variable Names
2. Document Your Circuit Interfaces
3. Handle All Computation States
4. Emit Events for Client Tracking
When Things Go Wrong
Here are the most common issues and how to fix them:“Type not found” Errors
- Typo in the circuit name - Check that MyCircuitexactly matches your#[instruction]function name (case matters!)
- You forgot to rebuild - Run arcium buildagain after making changes to your encrypted instructions
”No field found” Errors
field_0, field_1, etc. There’s no field called result unless you specifically named your function that way.
Try this instead:
Encryption Type Mismatches
Enc<Mxe, T> but your callback expects Enc<Shared, T> (or vice versa). Double-check your encrypted instruction’s return type - it needs to match what you’re expecting in the callback.
Callback Not Working? Check These:
- Circuit name matches exactly (case sensitive)
-  Ran arcium buildafter changing circuit
- Handling all ComputationOutputs variants
- Using correct field numbers (field_0, field_1, etc.)
- Array access within bounds (ciphertexts.len())
Finding Generated Types
The best way to see generated types:Array and Complex Type Handling
Fixed-Size Arrays
When your circuit returns arrays, each element becomes a separate scalar in the LEN count:SharedEncryptedStruct<3> because the array has 3 elements:
Nested Structures
For deeply nested data, LEN counts all scalar values at any depth:SharedEncryptedStruct<4> because Entity contains 4 total scalar values.
Migration from v0.1.x
If you’re upgrading from an older version, the new type generation system replaces manual byte parsing: Old way (v0.1.x):Type Generation Limitations
Supported Return Types
The type generation system works with most common Rust types, but has some constraints: ✅ Supported:- Primitive types: u8,u16,u32,u64,u128,i8,i16,i32,i64,i128,bool
- Fixed-size arrays: [T; N]where N is a compile-time constant
- Tuples: (T, U, V)with any number of elements
- Custom structs with supported field types
- Nested combinations of the above
- Dynamic types: Vec<T>,String,HashMap<K, V>
- Reference types: &T,&mut T(except for input parameters)
- Generic types with lifetime parameters
- Recursive or self-referencing structs
- Option<T>or- Result<T, E>as return types
Practical Constraints
Size Limitations:- Very large structs (1000+ fields) may impact compilation time
- Arrays with thousands of elements create correspondingly large LEN values
- Deep nesting (10+ levels) may cause macro expansion issues
Working Within Constraints
If you need unsupported types, consider these patterns:Common Patterns and Performance Tips
Choosing the Right Encryption Type
- Use Enc<Shared, T>when users need to decrypt and verify results (votes, game outcomes, personal data)
- Use Enc<Mxe, T>for internal state that users shouldn’t access (system secrets, aggregate statistics, protocol data)
Performance Considerations
- Large arrays: [u8; 1000]becomesSharedEncryptedStruct<1000>- consider if you really need all elements encrypted
- Complex nesting: Deep struct hierarchies increase LEN values - flatten when possible
- Mixed returns: (Enc<Shared, T>, Enc<Mxe, U>)creates separate encrypted structs for optimal access patterns
Testing Your Callbacks
Mock the computation outputs for testing:Quick Reference
| Return Type | Generated Struct | Access Pattern | 
|---|---|---|
| Enc<Shared, T> | SharedEncryptedStruct<1> | result.ciphertexts[0],result.nonce | 
| Enc<Mxe, T> | MXEEncryptedStruct<1> | result.ciphertexts[0],result.nonce | 
| (T, U, V) | {Circuit}OutputStruct0 | result.field_0,result.field_1 | 
| Custom struct | {Circuit}OutputStruct0 | result.field_0,result.field_1 | 
The callback type generation system automatically handles encrypted computation results, eliminating manual byte parsing and offset tracking. With properly typed structs, you can work directly with structured data and focus on building your applications rather than handling low-level data conversion. These generated types provide type safety and predictable patterns that make working with encrypted computation outputs straightforward and reliable.