Language Features

Psy Smart Contract Language provides a comprehensive set of features designed for zero-knowledge circuit development. This page outlines all major language features with examples and explanations.

Basic Types

Psy supports a range of primitive and composite types optimized for ZK circuit generation.

Primitive Types

Felt

The fundamental numeric type representing a field element in the Goldilocks field.

#![allow(unused)]
fn main() {
let value: Felt = 42;
let negative: Felt = -10;
let arithmetic: Felt = value + negative * 2;
}

Boolean

Boolean values for logical operations.

#![allow(unused)]
fn main() {
let flag: bool = true;
let condition: bool = false;
let result: bool = flag && !condition;
}

u32

32-bit unsigned integer type for efficient arithmetic operations.

#![allow(unused)]
fn main() {
let count: u32 = 100u32;
let mask: u32 = 0xFFFFu32;
let shifted: u32 = count << 2;
}

Composite Types

Arrays

Fixed-size arrays with compile-time known lengths.

#![allow(unused)]
fn main() {
let numbers: [Felt; 4] = [1, 2, 3, 4];
let matrix: [[Felt; 2]; 2] = [[1, 2], [3, 4]];
let element: Felt = numbers[0];
}

Tuples

Heterogeneous collections of values with full support for nesting, mutation, and complex access patterns.

#![allow(unused)]
fn main() {
// Basic tuple creation and access
let pair: (Felt, bool) = (42, true);
let triple: (Felt, Felt, bool) = (1, 2, false);
let first: Felt = pair.0;
let flag: bool = pair.1;

// Mutable tuples
let mut coordinates: (Felt, Felt) = (10, 20);
coordinates.0 = 15;  // Modify x coordinate
coordinates.1 = 25;  // Modify y coordinate
coordinates = (30, 40);  // Replace entire tuple

// Nested tuples
let nested: ((Felt, Felt), (Felt, Felt)) = ((1, 2), (3, 4));
let deeply_nested: (((Felt, Felt), Felt), Felt) = (((5, 6), 7), 8);

// Complex nested tuple manipulation
let mut complex_tuple: ((Felt, Felt), (Felt, Felt)) = ((9, 10), (11, 12));
complex_tuple.0.1 = 42;     // Modify first tuple's second element
complex_tuple.1 = (20, 21); // Replace entire second tuple

// Tuples in structs
struct TupleHolder {
    pub pair: (Felt, Felt),
    pub nested: ((Felt, Felt), Felt),
}

let mut holder: TupleHolder = new TupleHolder {
    pair: (22, 23),
    nested: ((24, 25), 26),
};
holder.pair.1 = 99;          // Modify tuple field in struct
holder.nested.0.0 = 88;      // Modify nested tuple in struct

// Functions returning tuples
fn create_tuple(x: Felt, y: Felt) -> (Felt, Felt) {
    (x + 1, y + 1)
}

// Functions taking tuples as parameters
fn sum_tuple(input: (Felt, Felt)) -> Felt {
    input.0 + input.1
}

let result_tuple: (Felt, Felt) = create_tuple(30, 31);
let sum_result: Felt = sum_tuple((40, 50));

// Tuples with different types
let mixed: (Felt, bool, u32) = (100, true, 42u32);
let complex_mixed: ((Felt, bool), (u32, Felt)) = ((1, false), (10u32, 2));
}

Tuple Features:

  • Type Safety: Each tuple element has a specific type
  • Indexed Access: Use .0, .1, .2, etc. to access elements
  • Mutation Support: Both individual elements and entire tuples can be modified
  • Arbitrary Nesting: Tuples can contain other tuples to any depth
  • Function Integration: Can be passed to and returned from functions
  • Struct Fields: Tuples can be used as struct field types

Note: Tuple destructuring (pattern matching like let (x, y) = tuple) is not currently supported, but individual element access provides full functionality.

Structs

User-defined composite types with named fields.

#![allow(unused)]
fn main() {
pub struct Person {
    pub age: Felt,
    male: bool,
}

let person: Person = new Person {
    age: 25,
    male: true,
};
}

Hash Type

Special 4-element array type for cryptographic operations.

#![allow(unused)]
fn main() {
let data: Hash = [1, 2, 3, 4];
let result: Hash = hash(data);
}

Functions

Functions are first-class citizens with support for parameters, return types, and inlining.

Basic Functions

#![allow(unused)]
fn main() {
fn add(a: Felt, b: Felt) -> Felt {
    a + b
}

fn greet() {
    // No return value
}
}

Function Calls and Inlining

All function calls are inlined by default for optimal circuit generation.

#![allow(unused)]
fn main() {
fn multiply(x: Felt, y: Felt) -> Felt {
    x * y
}

fn complex_calculation(a: Felt, b: Felt, c: Felt) -> Felt {
    // These calls get inlined at compile time
    let product = multiply(a, b);
    add(product, c)
}
}

Closures

Anonymous functions that can capture variables from their environment.

fn main() -> Felt {
    let multiplier: Felt = 3;
    
    let triple = |x: Felt| -> Felt {
        x * multiplier  // Captures 'multiplier' from environment
    };
    
    triple(5)  // Returns 15
}

Constants

Compile-time constant values for improved optimization.

#![allow(unused)]
fn main() {
const MAX_USERS: Felt = 1000;
const PI_APPROXIMATION: Felt = 3141;
const DEFAULT_AMOUNT: Felt = 100;

fn validate_user_count(count: Felt) -> bool {
    count <= MAX_USERS
}

fn calculate_with_constant() -> Felt {
    DEFAULT_AMOUNT + PI_APPROXIMATION
}
}

Comptime

Compile-time evaluation for constant folding and optimization.

#![allow(unused)]
fn main() {
// Complex constant expressions are evaluated at compile time
const COMPLEX_CALC: Felt = ((100 + 200) * 3 - 50) / 2 + (10 * 10);

fn use_constants() -> Felt {
    // All constant arithmetic is folded during compilation
    COMPLEX_CALC + 42
}
}

Control Flow

If Expressions

Conditional expressions that are flattened into arithmetic selection during compilation.

#![allow(unused)]
fn main() {
fn min(a: Felt, b: Felt) -> Felt {
    if a < b {
        a
    } else {
        b
    }
}

// Supports complex nested conditions
fn classify_number(x: Felt) -> Felt {
    if x > 100 {
        if x > 1000 {
            3  // Very large
        } else {
            2  // Large
        }
    } else {
        1  // Small
    }
}
}

Block Expressions

Scoped code blocks that return values.

#![allow(unused)]
fn main() {
fn calculate() -> Felt {
    let result = {
        let temp1 = 10;
        let temp2 = 20;
        temp1 + temp2  // Block returns this value
    };
    result * 2
}
}

Match Expressions

Pattern matching for control flow.

#![allow(unused)]
fn main() {
fn process_status(status: Felt) -> Felt {
    match status {
        0 => 10,        // Pending
        1 => 20,        // Processing  
        2 => 30,        // Complete
        _ => 0,         // Unknown
    }
}
}

Loops

Bounded loops that are completely unrolled at compile time.

#![allow(unused)]
fn main() {
fn sum_range() -> Felt {
    let mut total: Felt = 0;
    let mut i: Felt = 1;
    
    while i <= 5 {  // Fixed bound - unrolled at compile time
        total += i;
        i += 1;
    }
    
    total  // Returns 15
}
}

Modules

Code organization and namespace management.

Module Definition

#![allow(unused)]
fn main() {
mod math {
    pub fn add(a: Felt, b: Felt) -> Felt {
        a + b
    }
    
    fn private_helper() -> Felt {
        42
    }
}
}

Module Usage

use math::*;

fn main() -> Felt {
    add(10, 20)  // Using imported function
}

Nested Modules

#![allow(unused)]
fn main() {
mod crypto {
    pub mod hash {
        pub fn poseidon(input: Hash) -> Hash {
            hash(input)
        }
    }
    
    pub mod signature {
        pub fn verify(msg: [u64; 4], sig: [u32; 16], pubkey: [u32; 16]) -> bool {
            __secp256k1_verify(pubkey, msg, sig)
        }
    }
}
}

Traits

Interface definitions for shared behavior across types.

Basic Traits

#![allow(unused)]
fn main() {
pub trait Arithmetic {
    fn add(self, other: Self) -> Self;
    fn multiply(self, other: Self) -> Self;
}

pub trait Display {
    fn show(self) -> Felt;
}
}

Trait Implementation

#![allow(unused)]
fn main() {
struct Point {
    x: Felt,
    y: Felt,
}

impl Arithmetic for Point {
    fn add(self, other: Point) -> Point {
        new Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
    
    fn multiply(self, other: Point) -> Point {
        new Point {
            x: self.x * other.x,
            y: self.y * other.y,
        }
    }
}
}

Generics

Type parameters for code reuse and type safety.

Generic Functions

#![allow(unused)]
fn main() {
fn identity<T>(value: T) -> T {
    value
}

fn pair<T, U>(first: T, second: U) -> (T, U) {
    (first, second)
}
}

Generic Structs

#![allow(unused)]
fn main() {
struct Container<T> {
    value: T,
}

impl<T> Container<T> {
    pub fn new(value: T) -> Container<T> {
        new Container { value }
    }
    
    pub fn get(self) -> T {
        self.value
    }
}
}

Generic Traits

#![allow(unused)]
fn main() {
trait Convert<T> {
    fn convert(self) -> T;
}

impl Convert<Felt> for u32 {
    fn convert(self) -> Felt {
        self as Felt
    }
}
}

Type System Features

Type Checking

Static type checking ensures type safety at compile time.

#![allow(unused)]
fn main() {
fn type_safe_function(x: Felt, y: bool) -> (Felt, bool) {
    // Compiler verifies all types match
    let result_x: Felt = x + 1;      // ✓ Felt arithmetic
    let result_y: bool = y && true;  // ✓ Boolean logic
    (result_x, result_y)
}
}

Type Hints

Explicit type annotations for clarity and optimization.

#![allow(unused)]
fn main() {
fn with_type_hints() {
    let inferred = 42;              // Type inferred as Felt
    let explicit: Felt = 42;        // Explicit type annotation
    let tuple: (Felt, bool) = (1, true);  // Tuple type hint
}
}

Trait Constraints

Generic type constraints using trait bounds.

#![allow(unused)]
fn main() {
fn generic_add<T: Arithmetic>(a: T, b: T) -> T {
    a.add(b)  // T must implement Arithmetic trait
}

fn display_value<T: Display + Clone>(value: T) -> Felt {
    value.show()  // T must implement both Display and Clone
}
}

Storage and Smart Contracts

Persistent state management for blockchain applications.

Storage Structs

#![allow(unused)]
fn main() {
#[storage]
struct TokenContract {
    // Contract state fields
}

impl TokenContract {
    pub fn mint(amount: Felt) -> Felt {
        let user_id = get_user_id();
        let current_state = get_state_hash_at(user_id);
        let current_balance = current_state[0];
        
        let new_balance = current_balance + amount;
        cset_state_hash_at(user_id, [new_balance, current_state[1], current_state[2], current_state[3]]);
        
        new_balance
    }
}
}

Built-in Functions

ZK-optimized cryptographic and system functions.

Cryptographic Functions

#![allow(unused)]
fn main() {
// Poseidon hash
let data: Hash = [1, 2, 3, 4];
let hash_result: Hash = hash(data);

// ECDSA signature verification
let pubkey = [/* 16 u32 values */];
let message = [/* 4 u64 values */];  
let signature = [/* 16 u32 values */];
let is_valid: bool = __secp256k1_verify(pubkey, message, signature);
}

System Functions

#![allow(unused)]
fn main() {
// State access functions
let user_id: Felt = get_user_id();
let contract_id: Felt = get_contract_id();
let checkpoint: Felt = get_checkpoint_id();
let user_state: Hash = get_state_hash_at(user_id);
}

Development Tools

Dargo Package Manager

Complete project lifecycle management.

# Initialize new project
dargo init

# Create new project
dargo new my_project

# Compile contract
dargo compile --contract-name MyContract --method-names deploy transfer

# Execute with parameters  
dargo execute --contract-name MyContract --method-names mint --parameters 100

# Run tests
dargo test

# Format code
dargo fmt src/main.psy

Language Server Protocol (LSP)

IDE integration for enhanced development experience.

Supported Features:

  • Hover - Type information and documentation
  • Go to Definition - Navigate to symbol definitions
  • Find References - Locate all symbol usages
  • Code Formatting - Automatic code formatting
  • Error Diagnostics - Real-time error reporting

IDE Support:

  • Visual Studio Code
  • Neovim
  • RustRover/IntelliJ

Error Reporting

Precise error diagnostics with line and column information.

#![allow(unused)]
fn main() {
// Error example
fn invalid_function() {
    let x: Felt = true;  // Type mismatch error
    //    ^^^^     ^^^^ 
    //    |        |
    //    |        Expected Felt, found bool
    //    |
    //    Variable declared as Felt
}
}

Error Message:

error[E0308]: mismatched types
 --> src/main.psy:2:19
  |
2 |     let x: Felt = true;
  |            ----   ^^^^ expected `Felt`, found `bool`
  |            |
  |            expected due to this type annotation

Testing Framework

Built-in testing support with the #[test] attribute.

#![allow(unused)]
fn main() {
#[test]
fn test_arithmetic() {
    let result = add(2, 3);
    assert_eq(result, 5, "2 + 3 should equal 5");
}

#[test]
fn test_contract_mint() {
    // Test contract functionality
    let initial_balance = 100;
    let mint_amount = 50;
    let expected = initial_balance + mint_amount;
    
    let result = mint(mint_amount);
    assert(result > initial_balance, "Balance should increase after minting");
}
}

Package System (Crates)

Modular code organization and dependency management.

Dargo.toml Configuration

[package]
name = "my_contract"
version = "0.1.0"
edition = "2024"

[dependencies]
std = "0.1.0"
crypto = "0.2.0"

[lib]
name = "my_contract"
path = "src/lib.psy"

Library Structure

my_project/
├── Dargo.toml
├── src/
│   ├── main.psy
│   ├── lib.psy
│   └── utils/
│       ├── mod.psy
│       ├── math.psy
│       └── crypto.psy
└── tests/
    └── integration_test.psy

Zero-Knowledge Optimizations

All language features are designed with ZK circuit efficiency in mind:

  • Control flow flattening - Branches become arithmetic selections
  • Loop unrolling - Bounded loops are completely expanded
  • Function inlining - Eliminates call overhead
  • Constant folding - Compile-time evaluation
  • Dead code elimination - Unused code paths removed
  • Arithmetic optimization - Efficient field operations

This comprehensive feature set makes Psy a powerful language for developing efficient zero-knowledge smart contracts while maintaining familiar programming patterns and strong type safety.