Structs
A struct gathers several related pieces of data under one name, along with the functions, called methods, that work on them. It is how a Salam program describes the things it deals with: an account, an order, a point on a map.
Declaring a struct
Every field has a name, a type and, optionally, a default value. A method is a func written inside the struct, and inside it this refers to the value the method was called on. Fields and methods are private by default; pub makes them reachable from outside the struct.
struct Rectangle:
pub width: int = 1
pub height: int = 1
pub func area(): int:
ret this.width * this.height
end
end
func main:
plot := Rectangle { width = 12, height = 8 }
println "area of the plot:", plot.area()
endCreating and printing
A struct literal names the fields it sets: Point { x = 3, y = 4 }. Any field you leave out takes its default value. A field declared without a default has to be given every time, or the compiler stops with E028: struct literal 'P' requires field 'x'. A struct can be printed directly, with all of its fields:
struct Point:
pub x: int = 0
pub y: int = 0
end
func main:
p := Point { x = 3, y = 4 }
println p
println Point {}
endYour own text with to_str
The printed form shows every field, which is handy while you write a program but not always what a user should see. Give the struct a method pub func to_str(): str and println uses it instead. It is also used when the struct sits inside another struct, a Vector or an array.
struct Money:
pub cents: int = 0
pub func to_str(): str:
rest := this.cents % 100
pad := rest < 10 ? "0" : ""
ret "$" + (this.cents / 100) + "." + pad + rest
end
end
struct Item:
pub name: str = ""
pub price: Money = Money {}
end
func main:
pen := Item { name = "pen", price = Money { cents = 250 } }
println pen.price
println pen
println "total: " + Money { cents = 1205 }.to_str()
endItem has no to_str of its own, so it is printed field by field, but its price field uses the one from Money. To get the text without printing it, call to_str() yourself, as the last line does.
Public and private
A private field can only be read and written by the struct's own methods. Code outside that tries acc.balance gets E017: field 'balance' is private in struct 'Account' (mark it 'pub'). That is how a struct protects its rules: here nobody can make the balance negative, because the only way to change it is through deposit and withdraw, and both check the amount first.
struct Account:
owner: str = ""
balance: int = 0
pub static func open(owner: str): Account:
ret Account { owner = owner }
end
pub func owner_name(): str:
ret this.owner
end
pub func current(): int:
ret this.balance
end
pub mut func deposit(amount: int): bool:
if amount <= 0:
ret false
end
this.balance += amount
ret true
end
pub mut func withdraw(amount: int): bool:
if amount <= 0 or amount > this.balance:
ret false
end
this.balance -= amount
ret true
end
end
func main:
mut acc := Account.open("Sara")
println acc.deposit(500), acc.withdraw(800), acc.withdraw(120)
println acc.owner_name(), acc.current()
endA good habit is to make fields private unless there is a reason to expose them, and to give the outside world methods that say what can be done (deposit) rather than how the data is stored (balance).
A method cannot have the same name as a field of its struct. That is why the method that returns the balance is called current and not balance.
Mutating methods
A method that changes the struct's fields is declared mut func. As soon as a struct has one, the compiler checks every method: a plain func that assigns to a field of this is an error (E105: cannot modify 'this' in method 'reset': declare it 'mut func'), and a mut func can only be called on a variable declared with mut (E106). So by reading a method's first line you know whether it can change the value.
struct Counter:
value: int = 0
pub func read(): int:
ret this.value
end
pub mut func increment():
this.value += 1
end
end
func main:
mut c := Counter {}
c.increment()
c.increment()
println c.read()
endStatic members and constructors
static func and const inside a struct belong to the type rather than to one value; call them through the struct's name. A static func has no this, but it can set private fields, which makes it the natural place for constructors. Account.open in the example above is one; here is another that builds a ready-made value:
struct Color:
pub red: int = 0
pub green: int = 0
pub blue: int = 0
pub const MAX := 255
pub static func gray(value: int): Color:
ret Color { red = value, green = value, blue = value }
end
end
func main:
white := Color.gray(Color.MAX)
println white
endStructs are values
Assigning a struct copies it. After goal := start the two are separate values, and changing goal leaves start alone. Passing a struct to a function copies it too, so a function that should change the caller's struct takes it with &:. Two structs compare with == and != field by field, with no code of your own.
struct Point:
pub x: int = 0
pub y: int = 0
end
func reset_copy(p: Point):
p.x = 0
end
func reset(p &: Point):
p.x = 0
end
func main:
start := Point { x = 3, y = 4 }
mut goal := start
goal.y = 10
println start, goal
println start == Point { x = 3, y = 4 }, start == goal
reset_copy(goal)
println goal.x
reset(goal)
println goal.x
endreset_copy changed its own copy, which was thrown away when it returned. reset took a reference and changed the caller's goal. A &: parameter is also cheaper for a large struct, because nothing is copied. One exception to "a struct is a value": a Vector or a HashMap field is copied as a handle, so both copies share the same elements (see sharing and copying).
Composition with use
use places one struct inside another and makes its public fields and methods reachable directly. That is composition instead of inheritance: a Dog is built from an Animal plus a breed.
struct Animal:
pub name: str = ""
pub func introduce(): str:
ret "I am " + this.name
end
end
struct Dog:
pub use Animal
pub breed: str = ""
end
func main:
d := Dog { name = "Rex", breed = "hound" }
println d.introduce()
println d.name, d.breed
endThe embedded struct is also a field of its own, named after its type. If the outer struct defines a method with the same name as an embedded one, its own method wins, and it can still call the embedded version through that field:
struct Timestamps:
pub created: str = ""
pub updated: str = ""
pub func describe(): str:
ret "created " + this.created + ", updated " + this.updated
end
end
struct Post:
pub use Timestamps
pub title: str = ""
pub func describe(): str:
ret this.title + " (" + this.Timestamps.describe() + ")"
end
end
func main:
mut post := Post { title = "Hello", created = "2026-10-01" }
post.updated = "2026-10-05"
println post.describe()
println post.created, post.Timestamps.updated
println post
endA literal can set embedded fields directly (created = ...), and printing shows the embedded struct as a nested value. pub use makes the embedded struct visible outside; a plain use keeps it private.
Where to go next
- A struct can define operators such as
+and==as methods; see operator overloading. matchcan take a struct apart by its fields; see struct patterns.- Generic structs such as
Box<T>and interfaces that several structs share are in generics and interfaces.
Rules of thumb
- Give fields sensible defaults, so a literal only names what differs.
- Keep fields private and expose methods; use
pubfields for plain data with no rules to protect. - Mark every method that changes the struct
mut func, and declare the variable you call it on withmut. - Put constructors in a
static func, especially when the value needs checking or has private fields. - Assignment and parameters copy a struct. Use
&:when a function must change the caller's value.