Skip to main content
Arcis supports many of Rust’s native operations and extends them for confidential data, allowing you to write confidential 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, if let, match, for loops, arithmetic, comparisons, iterators (except filter) Doesn’t work: while, loop, break, continue, return, let ... else, .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) = ...Partial SupportSee pattern matching. Let chains require Rust edition 2024.
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 { ... }Partial SupportSee pattern matching. Last arms cannot have guards.
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 branches count: In MPC, both sides of non-constant conditional execution are evaluated, including if/else branches and non-constant match arms. 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, BaseField25519
a - bIntegers, floats, BaseField25519
a * bIntegers, floats, BaseField25519
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, BaseField25519
a <= bBooleans, integers, floats, BaseField25519
a >= bBooleans, integers, floats, BaseField25519
a > bBooleans, integers, floats, BaseField25519
a += bIntegers, floats, BaseField25519
a -= bIntegers, floats, BaseField25519
a *= bIntegers, floats, BaseField25519
a /= bIntegers, floats
a %= bIntegers
a ^= bBooleans
a &= bBooleans
a |= bBooleans
a <<= bNone
a >>= bIntegers, if b is known at compile time
BaseField25519 does not support /, %, >>, or <<. Note that &, |, ^ are booleans-only across all types. Use .field_division() or .euclidean_division() for division on field elements. See BaseField25519 Operations.

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 confidential 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: BaseField25519) to create a public key from its Curve25519 Montgomery X-coordinate.
  • ArcisX25519Pubkey::to_x() -> BaseField25519 to extract the Montgomery X-coordinate from a public key.
  • BaseField25519::from_u8(x)BaseField25519::from_u128(x) to convert unsigned integers to field elements. Signed variants (from_i8from_i128) and from_bool, from_usize, from_isize also available.
  • BaseField25519::power_of_two(exp) to compute 2^exp as a field element.

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:
  • debug_assert!, debug_assert_ne!, debug_assert_eq! to assert conditions during debugging. They do not change instruction behavior.
  • eprint!, eprintln!, print!, println! to print debug output. They do not change instruction behavior.
  • matches!(expr, pattern) to test a pattern and return bool. See Pattern support.
  • arcis_static_panic!(message) to fail compilation when the branch is reached. Useful for enforcing constraints that must be known before circuit generation.
  • include_bytes!("file_path") to include raw bytes from a file in Arcis circuits.
  • include!("file_path") to include a file in item position (not expression position).
  • assert_current_module!(crate::path::to::module) to enable crate:: absolute paths within the current module. Place at the top of any module that needs to reference items via absolute paths.
  • encrypted_mod!("path/to/module.rs") or encrypted_mod!("path/to/module.rs", alias_name) to use another file as a module within an #[encrypted] module. The target file must use the #[encrypted_library] attribute. Items are accessible via the filename stem (or alias) as a namespace, e.g., module_name::ITEM.
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), .as_slice(), .as_mut_slice() 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 a conditionally executed block (if/else, non-constant match arm, or guard)
  • .to_arcis() on Encs
  • .from_arcis(x) on Owners (objects of types Mxe or Shared) if not inside a conditionally executed block (if/else, non-constant match arm, or guard)
  • .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 reveal when the key is shared across inputs (avoids duplicate reveal gates). See EncData for details.
  • .data on Enc<Owner, T> to extract only the EncData<T> ciphertext for smaller callback payloads.
  • .safe_inverse() on BaseField25519 to get the field inverse (returns 0 for inverse of 0).
  • .field_division(divisor) on BaseField25519 for field division (returns 0 for division by 0).
  • .euclidean_division(divisor) on BaseField25519 for signed Euclidean division (panics on division by 0).
  • .to_u8_unchecked().to_u128_unchecked() on BaseField25519 to extract as unsigned int. Silently produces incorrect results if value exceeds target range. Signed variants (to_i8_uncheckedto_i128_unchecked) and to_bool_unchecked also available.

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 are inside the #[encrypted] area. Both super:: paths and crate:: paths (when assert_current_module! is declared) are supported.
  • std::mem::replace and std::mem::swap
  • Box::leak, Box::new, Cell::new, Cell::from_mut

Code organization with modules

Arcis supports nested modules, super:: parent references, crate:: absolute paths, and multi-file projects. #[instruction] functions can be placed at any module depth, not just at the top level. Submodules with super:: and crate:: paths:
use arcis::*;

#[encrypted]
mod my_mxe {
    use arcis::*;
    assert_current_module!(crate::my_mxe);

    const THRESHOLD: u64 = 1000;

    // No assert_current_module! needed — this module only uses super::, not crate::
    mod validation {
        use arcis::*;

        pub fn is_above_threshold(val: u64) -> bool {
            val > super::THRESHOLD  // Access parent constant via super::
        }
    }

    mod processing {
        use arcis::*;
        assert_current_module!(crate::my_mxe::processing);

        // #[instruction] works in nested modules
        #[instruction]
        pub fn process(vals: [u64; 3]) -> [u64; 3] {
            let mut result = [0u64; 3];
            for i in 0..3 {
                // Use crate:: to reference a sibling module's function
                let valid = crate::my_mxe::validation::is_above_threshold(vals[i]);
                result[i] = if valid { vals[i] * 2 } else { vals[i] };
            }
            result
        }
    }
}
Multi-file projects with encrypted_mod!:
// encrypted-ixs/src/lib.rs
use arcis::*;

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

    encrypted_mod!("helpers.rs");

    type Config = helpers::Config;

    #[instruction]
    fn compute(cfg: Config) -> u64 {
        cfg.value * helpers::MULTIPLIER
    }
}
// encrypted-ixs/src/helpers.rs
use arcis::*;

#[encrypted_library]
mod arcis_library {
    pub const MULTIPLIER: u64 = 42;

    pub struct Config {
        pub value: u64,
    }
}
Each file imported via encrypted_mod! must use #[encrypted_library] (not #[encrypted]). Items are accessible through the filename stem as a namespace (e.g., helpers::Config), or you can provide an alias: encrypted_mod!("helpers.rs", utils) makes items accessible as utils::Config.

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, let statements, if let conditions, matches! calls, and match expressions:
  • 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 _ = ...;
| patterns are supported only in if let, matches!, and match; they cannot be used on a match arm that also has a guard. The .. pattern is only supported inside struct patterns with named fields, e.g. MyStruct { x: 0, .. }. Literal, range, and path-constant patterns are only supported in if let, matches!, and match. Path constants must use a path such as module::MY_CONST; bare const identifiers are not supported as patterns.

Pattern matching

Arcis supports match expressions, if let (including let chains), and the matches! macro for branching on patterns. Patterns can be literals, ranges, path constants like module::MY_CONST, OR-patterns, tuples, structs (with .. rest), arrays/slices, references, bindings, and wildcards. Match arms can use if-guards, except an arm cannot combine a guard with an OR-pattern.
let ... else remains unsupported. In match, guards are supported, but an arm cannot combine a guard with an OR-pattern.

match expressions

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

    pub struct Point { x: i16, y: i16 }

    #[instruction]
    pub fn classify(p: Point) -> i16 {
        match p {
            Point { x: 0, y: 0 } => 0,
            Point { x: 0, y }    => y,
            Point { x, y: 0 }    => x * 10,
            Point { x, y }       => x + y,
        }
    }

    #[instruction]
    pub fn bucket(x: u8) -> u8 {
        match x {
            v if v < 5  => 0,
            v if v < 10 => 1,
            _           => 2,
        }
    }
}

if let and let chains

if let Point { x: 0, y } = p {
    y
} else {
    -1
}

// let chain: bind in the first condition, use in the second
if let (0, val) = (a, b) && val < 10 {
    1
} else {
    0
}
Let chains (if let ... && let ... && ...) require Rust edition 2024. The default arcium init scaffold sets edition = "2021" in encrypted-ixs/Cargo.toml — bump it to edition = "2024" to use this syntax.

matches! macro

matches! returns a bool and supports the same pattern surface:
let in_range  = matches!(x, 0..=9);
let is_origin = matches!(p, Point { x: 0, y: 0 });

let is_short  = matches!(arr.as_slice(), [] | [_]);

Slice patterns

Fixed-size arrays viewed as slices can be matched with arms of different lengths:
match arr.as_slice() {
    []          => 0,
    [x]         => *x + 1,
    [x, y]      => *x + *y + 2,
    [_, _, _]   => 3,
    _           => 99,
}
Item shadowing of a let binding is rejected. Defining fn f() or const F: _ after let f = ... (or let F = ...) errors at compile time with “Cannot have an item with the same name as a variable in scope.” let shadowing another let is still allowed.

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.

Best practices

Performance tips, debugging, and testing strategies.