Skip to main content
Arcis supports many of Rust’s native operations and extends them for encrypted data, allowing you to write private computations using familiar Rust syntax. See the tables below for a detailed list of supported and unsupported operations.
Use this page when you need to check if a specific operation is supported in Arcis circuits.

Quick Summary

Works: if/else, for loops, arithmetic, comparisons, iterators (except filter) Doesn’t work: while, loop, break, match, return, .filter() See tables below for full details.

Table of contents

Expression support

Expression NameExampleSupportComments
Array literal[a, b]Supported
Assignmenta = b;Supported
Async blockasync { ... }Unsupported
Awaitfoo().awaitUnsupported
Binary expressiona + bPartial SupportSee table below for supported binary expressions.
Block expression{ ... }Supported
Breakbreak;Unsupported
Function callf(a, b)Partial SupportSee table below for supported functions.
Castsa as u16Partial SupportSee table below for supported conversions.
Closures|a, b | a + bSupported
Const blockconst { ... }Supported
Continuecontinue;Unsupported
Field access/setobj.fieldSupported
For loopfor i in expr { ... }SupportedNote that expr will have its length known at compile-time.
Ifif cond { ... } else { ... }SupportedComplexity is O(then_block + else_block).
Indexinga[idx]SupportedComplexity is O(a.len()) if idx isn’t compile-time known (all positions are checked to hide which index was accessed).
If letif let Some(x) = ...Unsupported
Literals1u128Partial SupportSee table below for supported literals.
Loopsloop { ... }UnsupportedMPC circuits have fixed structure—variable iteration counts would require dynamic circuit size. Use for with compile-time bounds instead.
Macrosprintln!("{}", q)Partial SupportSee table below for supported macros.
Matchmatch n { ... }Unsupported
Method callsx.foo(a, b)Partial SupportSee table below for supported methods.
Parentheses(a + b)Supported
PathsFoo::barPartial SupportSee table below for supported paths.
Ranges4..5Partial SupportNot supported in arr[4..16].
Raw addresses&raw const fooUnsupported
References&mut fooSupported
Repeat arrays[4u8; 128]Supported
Returnreturn false;Unsupported
Struct literalsMyStruct { a: 12, b }Supported
Try expressionthis_call_can_err()?;Unsupported
Tuple literal(a, 4, c)Supported
Unary expressions!xPartial SupportUser-defined unary operations are not supported.
Unsafeunsafe { ... }Unsupported
While loopswhile x < 64 { ... }UnsupportedCannot be supported as the number of iterations is not known.
Why both branches count: In MPC, both if and else branches are always evaluated—the condition only selects which result to use. This ensures the execution pattern does not leak information about the condition value. See Thinking in MPC for details.

Binary expressions

User-defined binary operations are currently unsupported.
ExampleSupported types
a + bIntegers, floats
a - bIntegers, floats
a * bIntegers, floats
a / bIntegers, floats
a % bIntegers
a && bBooleans
a || bBooleans
a ^ bBooleans
a & bBooleans
a | bBooleans
a << bNone
a >> bIntegers, if b is known at compile time.
a == bAll. Use derive(PartialEq) for structs.
a != bAll. Use derive(PartialEq) for structs.
a < bBooleans, integers, floats
a <= bBooleans, integers, floats
a >= bBooleans, integers, floats
a > bBooleans, integers, floats
a += bIntegers, floats
a -= bIntegers, floats
a *= bIntegers, floats
a /= bIntegers, floats
a %= bIntegers
a ^= bBooleans
a &= bBooleans
a |= bBooleans
a <<= bNone
a >>= bIntegers, if b is known at compile time

Cast expressions

a as MyType is only supported:
From TypeTo Type
integer typeinteger type
boolinteger type
integer typebool
&...&T&T

Function calls

The following function calls are supported:
  • user-defined function calls (without recursion)
  • ArcisRNG::bool() to generate a boolean.
  • ArcisRNG::gen_uniform::<T>() to generate a uniform value of type T (bool, integer, or combination). Requires explicit type parameter.
  • ArcisRNG::gen_integer_from_width(width: usize) -> u128. Generates a secret integer between 0 and 2^width - 1 included.
  • ArcisRNG::gen_public_integer_from_width(width: usize) -> u128. Generates a public integer between 0 and 2^width - 1 included.
  • ArcisRNG::gen_integer_in_range(min: u128, max: u128, n_attempts: usize) -> (u128, bool). Generates a random integer in [min, max] using rejection sampling. n_attempts must be compile-time known. Returns (result, success) where success=false indicates all attempts were rejected. With n_attempts=24, failure probability is <2^-24.
  • ArcisRNG::shuffle(slice) on slices. Complexity is in O(n*log³(n) + n*log²(n)*sizeof(T)).
  • Mxe::get() to be able to create MXE-owned secret data.
  • Shared::new(arcis_public_key) to share private data with arcis_public_key.
  • ArcisX25519Pubkey::from_base58(base58_byte_string) to create a public key from a base58-encoded address.
  • ArcisX25519Pubkey::from_uint8(u8_byte_slice) to create a public key from a Uint8 array.
  • SolanaPublicKey::from_serialized(value) to create a Solana public key from serialized form.
  • SolanaPublicKey::from_base58(byte_string) to create a Solana public key from base58.
  • ArcisMath::sigmoid(x) for the sigmoid activation function.
  • LogisticRegression::new(coef, intercept) for logistic regression models.
  • LinearRegression::new(coef, intercept) for linear regression models.
  • Pack::new(value) to bit-pack data for onchain storage (multiple small values fit into fewer field elements).
  • ArcisX25519Pubkey::new_from_x(x: BaseField) to create a public key from its Curve25519 Montgomery X-coordinate.
  • ArcisX25519Pubkey::to_x() to extract the Montgomery X-coordinate from a public key.

Literal expressions

ExampleSupport
"foo"Unsupported
b"foo"Supported
c"foo"Unsupported
b'f'Supported
'a'Unsupported
1Supported
1u16Supported
1f64Supported
1.0e10f64Supported
trueSupported

Macros

The following macros are supported to help debug your Rust code:
  • debug_assert!, debug_assert_ne!, debug_assert_eq!. They do not change instruction behavior and are only useful for debugging your Rust code.
  • eprint!, eprintln!, print!, println!. They do not change instruction behavior and are only useful for debugging your Rust code.
  • arcis_static_panic!(message) to fail compilation when the branch is reached. Useful for enforcing constraints that must be known before circuit generation.
Example usage:
const ARRAY_LEN: usize = 3; // Change to 1 and the example will not compile.

fn second_element(arr: &[u8]) -> u8 {
    if arr.len() < 2 {
        arcis_static_panic!("Array must have at least 2 elements");
    }
    arr[1]
}

#[instruction]
fn reveal_second_element(input: Enc<Shared, Pack<[u8; ARRAY_LEN]>>) -> u8 {
    let array = input.to_arcis().unpack();
    second_element(&array).reveal()
}
arcis_static_panic! triggers at compile time when the Arcis compiler evaluates the branch. Try changing ARRAY_LEN to 1 above—the compile error demonstrates how this macro enforces constraints that must be validated before circuit generation.

Method calls

The following method calls are supported:
  • user-defined method calls (with generics but without recursion)
  • .clone() on all Clone objects.
  • .len(), .is_empty(), .swap(a, b), .fill(value), .reverse(), .iter(), .iter_mut(), .into_iter(), .windows(width), .copy_from_slice(src), .clone_from_slice(src), .split_at(mid), .split_at_mut(mid), .rotate_left(mid), .rotate_right(mid), .contains(item), .starts_with(needle), .ends_with(needle) on arrays and slices.
  • .sort() on arrays of integers. Complexity is in O(n*log²(n)*bit_size).
  • .enumerate(), .chain(other), .cloned(), .copied(), .count(), .rev(), .zip(other), .map(func), .for_each(func), .fold(init, func), .sum(), .product() on iterators.
  • .take(n), .skip(n), .step_by(n) on iterators when n is compile-time known.
  • .reveal() if not inside an if or else block where the condition is not a compile-time constant
  • .to_arcis() on Encs
  • .from_arcis(x) on Owners (objects of types Mxe or Shared) if not inside an if or else block where the condition is not a compile-time constant
  • .abs(), .min(x), .max(x) on integers and floats
  • .abs_diff(other), .is_positive(), .is_negative(), .div_ceil(other) on integers
  • .to_le_bytes(), .to_be_bytes() on typed integers (does not work on integers whose type the interpreter does not know)
  • .exp(), .exp2(), .ln(), .log2(), .sqrt() on floats.
  • .unpack() on Pack<T> to extract the original value from packed storage.
  • .to_arcis_with_pubkey_and_nonce(pubkey, nonce) on EncData<T> to decrypt when the key is shared across inputs (avoids duplicate decryption gates).

Paths

The following paths are supported:
  • IntType::BITS, IntType::MIN and IntType::MAX where IntType is an integer type.
  • Paths to user-defined constants, functions and structs, as long as they don’t use the unsupported crate or super.
  • std::mem::replace and std::mem::swap

Item support

Item NameExampleSupportComments
Constantconst MAX: u16 = 65535Supported
Enumenum MyEnum { ... }Unsupported
Externextern ...Unsupported
Functionsfn foo() -> u8 { 0 }Partial SupportRecursive functions are not supported.
Implsimpl MyType { ... }SupportedGenerics and custom traits are supported. MyType must not be a reference.
Macro Definitionsmacro_rules! ...Unsupported
Macro Invocationsprintln!(...)Partial SupportSee table above for supported macros.
Modulesmod my_module { ... }Supported
Staticsstatic ...Unsupported
Structsstruct MyStruct { ... }Supported
Traitstrait MyTrait { ... }Partial SupportCustom traits with associated types and constants. Standard library traits forbidden.¹
Type Aliasestype MyId = usize;Supported
Unionunion MyUnion { ... }Unsupported
Useuse arcis::*Partial SupportOnly use arcis::* is supported.
Arcis Circuit#[arcis_circuit = "name"]SupportedUse a pre-built optimized circuit by name. For internal/advanced use.
¹ Forbidden trait implementations: You cannot manually implement Drop, Deref, AsRef, AsMut, From, Into, TryFrom, TryInto, PartialEq, Eq, PartialOrd, Ord, Clone, ToOwned, ToString, Default, Iterator, IntoIterator, DoubleEndedIterator, ExactSizeIterator, Extend, FromIterator, Fn, FnMut, FnOnce, Future, IntoFuture, AsyncFn, AsyncFnMut, or AsyncFnOnce.Why? These traits have special runtime semantics (drop ordering, lazy evaluation, dynamic dispatch) that cannot be correctly translated to fixed MPC circuits. The Arcis compiler provides built-in implementations that work within MPC constraints.Use #[derive(...)] for Clone, PartialEq, etc., which generates MPC-compatible implementations.

Pattern support

The following patterns are supported in function arguments and let statements:
  • simple idents: let ident = ...;
  • mutable idents: let mut ident = ...;
  • ref idents: let ref ident = ...;
  • mutable ref idents: let ref mut ident = ...;
  • parentheses around a supported pattern: let (...) = ...;
  • reference of a supported pattern: let &... = ...;
  • array of supported patterns: let [...] = ...;
  • struct of supported patterns: let MyStruct { ... } = ...;
  • tuple of supported patterns: let (...) = ...;
  • tuple struct of supported patterns: let MyStruct(...) = ...;
  • type pattern of a supported pattern: let ...: ty = ...;
  • wild pattern: let _ = ...;
In particular, the .. pattern is currently unsupported.

Generics

Arcis supports Rust generics with some constraints. Generic types must be known at compile time. Runtime polymorphism is not supported.

Generic Functions

#[encrypted]
mod generics_example {
    use arcis::*;

    // Use a pre-built optimized circuit by name
    // The empty function body is intentional - the circuit implementation is built-in
    #[arcis_circuit = "zero"]
    fn make_zero<T: ArcisType>(a: T) -> T {}

    fn set_zero<T: ArcisType + Copy>(a: &mut T) {
        *a = make_zero(*a);
    }

    #[instruction]
    fn zero_any_type(mut arr: [u8; 10], mut val: u64) -> ([u8; 10], u64) {
        set_zero(&mut arr);
        set_zero::<u64>(&mut val);  // Turbofish syntax works
        (arr, val)
    }
}

Generic Structs

struct Wrapper<T>(T);

impl<T> Wrapper<T> {
    fn new(value: T) -> Self {
        Wrapper(value)
    }

    fn into_inner(self) -> T {
        self.0
    }
}

#[instruction]
fn use_generic_struct(a: u8) -> u8 {
    Wrapper::new(a).into_inner()
}

Custom Traits

trait Processable {
    type Output;
    fn process(&self) -> Self::Output;
}

impl Processable for u8 {
    type Output = u16;
    fn process(&self) -> u16 {
        *self as u16 * 2
    }
}

fn apply_process<T: Processable>(val: &T) -> T::Output {
    val.process()
}

#[instruction]
fn trait_example(x: u8) -> u16 {
    apply_process(&x)
}

Generic Constraints

FeatureSupportedNotes
Type parameters <T>YesMust be known at compile time
Trait bounds T: TraitYesIncluding ArcisType
Associated typesYestype Output;
Associated constantsYesconst SIZE: usize;
Where clausesYeswhere T: Clone
Turbofish ::<T>YesFor explicit type specification
Runtime polymorphismNoNo dyn Trait or trait objects

Iterators

Most iterator methods work in Arcis, with the notable exception of .filter().

Supported Iterator Methods

#[instruction]
fn iterator_examples(arr: [u8; 10]) -> u16 {
    // Basic iteration
    let mut sum = 0u16;
    for val in arr.iter() {
        sum += *val as u16;
    }

    // Method chaining
    arr.iter()
       .map(|x| *x as u16)
       .map(|x| x * 2)
       .sum()
}

Complete Iterator Support

MethodSupportedNotes
.iter()YesCreates iterator of references
.iter_mut()YesMutable references
.into_iter()YesConsumes collection
.map(f)YesTransform elements
.enumerate()YesAdd indices
.zip(other)YesPair with another iterator
.chain(other)YesConcatenate iterators
.rev()YesReverse order
.cloned()YesClone elements
.copied()YesCopy elements
.fold(init, f)YesReduce with accumulator
.sum()YesSum all elements
.product()YesMultiply all elements
.count()YesCount elements
.take(n)Yesn must be compile-time known
.skip(n)Yesn must be compile-time known
.step_by(n)Yesn must be compile-time known
.for_each(f)YesApply function to each
.filter(f)NoWould produce variable-length output
.find(f)NoWould require early exit
.any(f)NoWould require early exit
.all(f)NoWould require early exit

Filter Alternative

Since .filter() is not supported (it produces variable-length output), use a manual loop with conditionals:
// ✗ Not supported
arr.iter().filter(|x| **x > threshold).sum()

// ✓ Manual filter pattern
#[instruction]
fn filter_sum(arr: [u8; 10], threshold: u8) -> u16 {
    let mut sum = 0u16;
    for val in arr.iter() {
        if *val > threshold {
            sum += *val as u16;
        }
    }
    sum
}
This pattern checks all elements but only accumulates those meeting the condition—same result, fixed execution structure.

What’s Next?

Primitives

RNG, cryptography, and data packing operations.