Skip to main content

Types

Ralph is a statically typed language, but you don't need to specify the type for local variables and constants thanks to type inference. All types of Ralph are value types, i.e. they are always copied when they are used as function arguments or assigned. Currently, Ralph only supports the following data types:

Primitive Types

U256

// The type of `a` ... `d` is U256.
let a = 10
let b = 10u
let c = 1_000_000_000
let d = 1e18

I256

// The type of `a` ... `d` is I256.
let a = -10
let b = 10i
let c = -1_000_000_000
let d = -1e18

Bool

// The type of `a` and `b` is Bool.
let a = false
let b = true

ByteVec

// ByteVec literals must start with `#` followed by a hex string.
let a = #00112233
// ByteVec concatenation
let b = #0011 ++ #2233 // `b` is #00112233
// Empty ByteVec
let c = #

Address

// Address literals must start with `@` followed by a valid base58 encoded Alephium address.
let a = @1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH

String

Ralph does not have a native type for strings, but you can define string literals which are encoded in ByteVec.

// String literals starts with `b`.
let a = b`Hello`
let b = b`World`
let c = a ++ b` ` ++ b

Tuple

Ralph supports tuples, which are values that contain fixed number of elements, each with its own type.

// Function that returns a tuple of 3 elements
fn foo() -> (U256, Boolean, ByteVec) {
return 1, false, #00
}

// Destructure the tuple, `a` is immutable, `b` is mutable.
// `_` is the anonymous variable to ignore the unnecessary variable
let (a, mut b, _) = foo()

Fixed Size Array

The syntax for fixed-size arrays is influenced by Rust.

// The type of `a0` is [U256; 4]
let a0 = [0, 1, 2, 3]

// The type of `a1` is [[U256, 2]; 2]
let a1 = [[0, 1], [2, 3]]

// The type of `a2` is [I256; 3]
let a2 = [0i; 3]

// The type of `a3` is [ByteVec; 4]
let a3 = [#00, #11, #22, #33]

Struct

In Ralph, Structs can be globally defined and can contain fields that are either mutable or immutable. However, to assign a value to a field, all of the field selectors must be mutable. E.g. in order to set foo.x.y.z = 123, all foo, x, y, and z must be mutable.

// Structs have to be defined globally
struct Foo { x: U256, mut y: U256 }
struct Bar { z: U256, mut foo: Foo }

Contract Baz() {
...

// f.y = 3 won't work as f is immutable despite the field y being mutable
let f = Foo { x: 1, y: 2 }

// ff = f won't work as ff.x is immutable despite ff and ff.y being mutable
let mut ff = Foo { x: 1, y: 2 }
ff.y = 3 // This works as both ff and y are mutable

// b.foo.y = 5 won't work as b is immutable
let b = Bar { z: 4, foo: f }

let mut bb = Bar { z: 5, foo: f }
bb.foo.y = 6 // This works as bb, foo, and y are all mutable

...
}

In Ralph, structs are value types, not reference types. When assigned to a new struct variable, it copies all fields to the new struct variable:

struct Foo { x: U256, mut y: U256 }
Contract Bar() {
pub fn bar() -> () {
let foo0 = Foo { x: 0, y: 1 }
let mut foo1 = foo0
foo1.x = 1 // modifying foo1.x will not change foo0.x
assert!(foo0.x == 0, 0)
assert!(foo1.x == 1, 0)
}
}

Ralph also supports destructuring struct, and the syntax is similar to TypeScript:

struct Foo { x: U256, y: U256 }
Contract Bar() {
pub fn bar() -> () {
let foo = Foo { x: 0, y: 1 }
let Foo { mut x, y } = foo
x = 1

// the variables `x` and `y` already exist, we create two new variables `x1` and `y1`
let Foo { x: x1, y: y1 } = foo
}
}

Map

In Ralph, Maps are defined as global contract attributes, eliminating the need for initialization. The syntax is mapping[KeyType, ValueType] mapName where the KeyType can be any primitive types (Bool, U256, I256, Address, ByteVec), and the ValueType can be any type.

Under the hood, each Map entry is constructed as a subcontract of the current contract. Therefore, creating a map entry entails a minimal contract deposit, easily done using the built-in function mapEntryDeposit!().

There are 3 essential built-in map methods insert!, remove!, contains! (see doc). Map values can be accessed and updated with the bracket syntax map[key] = newValue. Below are some examples illustrating their usage. For more comprehensive examples, refer to the blind-auction repository and the unit tests here.

Contract Counters() {
// All maps must be defined here with `mapping[KeyType, ValueType]`, before events and constants
mapping[Address, U256] counters

@using(preapprovedAssets = true)
pub fn create() -> () {
let key = callerAddress!()
let depositor = key
// The depositor will deposit a minimal ALPH deposit for the new map entry which is a subcontract
counters.insert!(depositor, key, 0)
}

pub fn count() -> () {
let key = callerAddress!()
let value = counters[key]
// Update the map entry value
counters[key] = value + 1
}

pub fn clear() -> U256 {
let key = callerAddress!()
let depositRecipient = key
let value = counters[key]
// Each map entry removal redeems the map entry deposit
counters.remove!(depositRecipient, key)
return value
}

pub fn contains() -> Bool {
// Check the existence of map entry
return counters.contains!(callerAddress!())
}
}

TxScript CreateCounter(counters: Counters) {
let from = callerAddress!()
counters.insert!{from -> ALPH: mapEntryDeposit!()}() // Approve minimal deposit for creating the map entry
}

Constants

Ralph supports defining constants of primitive types. Constant names must start with an uppercase letter. We recommend using the SCREAMING_SNAKE_CASE naming convention for constants:

const TOTAL_SUPPLY = 1e18

Ralph also supports compile-time evaluation of constants:

const A = 10
const B = 20
const C = A * B // A and B must be defined before C

Ralph allows you to define global constants that can be used in any contracts or scripts:

const TOTAL_SUPPLY = 1e18
const ERROR_CODE = 0

Contract Token() {
pub fn getTotalSupply() -> U256 {
return TOTAL_SUPPLY
}
}

TxScript Main(token: Token) {
assert!(token.getTotalSupply() == TOTAL_SUPPLY, ERROR_CODE)
}

You can also define contract-specific local constants. These constants can only be used within the contract and must not have the same names as global constants:

Contract Token() {
const TOTAL_SUPPLY = 1e18

pub fn getTotalSupply() -> U256 {
return TOTAL_SUPPLY
}
}

Enum

Similar to constant definitions, Ralph supports both global and local enum definitions:

enum GlobalErrorCode {
INVALID_CALLER_CONTRACT = 0
}

Contract Foo(owner: Address, parentId: ByteVec) {
enum LocalErrorCode {
INVALID_OWNER = 1
}

pub fn foo(caller: Address) -> () {
assert!(callerContractId!() == parentId, GlobalErrorCode.INVALID_CALLER_CONTRACT)
assert!(caller == owner, LocalErrorCode.INVALID_OWNER)
}
}