The Atlas77 Programming Language
by Gipson62
This version of the documentation is currently only experimental and thoughts I gathered while developing the language. It may not reflect the current state of the language.
The documentation as a whole will be available offline in the compiler itself in future updates.
Introduction
Welcome to The Atlas77 Programming Language, an experimental book about the Atlas77 programming language. Experimental because I’m trying to gather and lay down my thoughts about the language as I develop it to normalize the language in itself.
Atlas77 is a statically typed, compiled programming language running on a VM that I do in my free time. I hope to be able to use it for future projects. Don’t expect it to be production-ready anytime soon.
I would describe it as a modern take of C++. And for that it takes inspiration from many languages like:
- Rust/C++/C# for the syntax
- Java/CPython for the runtime
Goals
- Having a
core::graphicsmodule that links to Vulkan/OpenGL/DirectX. - Having a
core::sdlmodule that links to SDL. - Bootstrapping the compiler.
- Having a
core::ffimodule that allows interfacing with C libraries. - Having a
core::ffimodule that allows interfacing with Rust libraries. - Making a game with it.
- Linking to blue_engine (see blue_engine documentation).
- It should work 😁
Getting Started with Atlas 77
Welcome to Atlas 77! This guide will help you understand the basics of the language and write your first programs.
Your First Program
Here is a simple “Hello, Atlas!” program:
import "std/io";
fun main() {
println("Hello, Atlas!");
}
Save this code to a .atlas file, then run it with:
atlas_77 run <FILE_PATH>
For example:
atlas_77 run hello.atlas
Creating a Project
To create a new Atlas 77 project with standard structure:
atlas_77 init my_project
cd my_project
atlas_77 run
This creates a basic project template with all necessary files.
Core Concepts
1. Comments
Comments document code and are ignored by the compiler.
// This is a single-line comment
let x: int64 = 42; // Comments can appear at the end of lines
Note: Multi-line comments are not yet implemented.
2. Variables and Constants
Variables are declared with let (mutable) or const (immutable):
import "std/io";
fun main() {
let x: int64 = 5;
x = 10; // OK - mutable variable
println(x); // Output: 10
const y: int64 = 5;
// y = 10; // Error: Cannot assign to a constant
}
Type annotations are mandatory for constants, the compiler can infer types for mutable variables. It’s mandatory for the sake of readability.
3. Data Types
Atlas77 is statically and strongly typed.
Primitive Types
| Type | Description | Status |
|---|---|---|
int64 | 64-bit signed integer | ✅ |
uint64 | 64-bit unsigned integer | ✅ |
float64 | 64-bit floating point | ✅ |
bool | Boolean (true/false) | ✅ |
char | Unicode character | ✅ |
string | UTF-8 string | ✅ |
Other integer and float sizes (int8, int16, int32, float32, etc.) are planned but not yet fully implemented.
Strings
Strings are UTF-8 encoded. Length is measured in bytes, not characters:
import "std/io";
fun main() {
let greeting: string = "Hello, Atlas!";
println(greeting);
}
4. Functions
Functions are defined with the fun keyword and require explicit return types:
import "std/io";
fun add(x: int64, y: int64) -> int64 {
return x + y;
}
fun greet(name: string) -> unit {
println("Hello, " + name);
}
fun main() {
let result = add(5, 10);
println(result); // Output: 15
greet("Atlas"); // Output: Hello, Atlas
}
Use unit as the return type for functions that don’t return a value.
5. Control Flow
If-Else Statements
import "std/io";
fun main() {
let x = 5;
if x > 0 {
println("x is positive");
} else if x < 0 {
println("x is negative");
} else {
println("x is zero");
}
}
While Loops
import "std/io";
fun main() {
let i = 0;
while i < 5 {
println(i);
i = i + 1;
}
}
Note: For-loops and pattern matching are planned but not yet implemented.
6. Arrays
Arrays store multiple values of the same type with fixed size:
import "std/io";
fun main() {
let numbers: [int64] = [1, 2, 3, 4, 5];
let i = 0;
while i < 5 {
println(numbers[i]);
i = i + 1;
}
}
You can also allocate arrays with specific sizes:
import "std/io";
fun main() {
// Allocate an array of 5 int64s
let numbers = new [int64; 5];
let i = 0;
while i < 5 {
numbers[i] = i * 2;
println(numbers[i]);
i = i + 1;
}
}
7. Structs
Structs group related data together:
import "std/io";
struct Person {
public:
name: string;
age: int64;
Person(name: string, age: int64) {
this.name = name;
this.age = age;
}
fun greet(&this) {
println("Hello, my name is " + this.name);
}
}
fun main() {
let person = new Person("Alice", 30);
person.greet(); // Output: Hello, my name is Alice
}
Fields are private by default. Use public: to make them accessible outside the struct.
8. Enums
Enums define types with a set of named values:
import "std/io";
public enum Color {
Red = 1;
Yellow; // Auto-assigned: 2
Green = 3;
Purple; // Auto-assigned: 4
Blue = 5;
}
fun main() {
let color = Color::Red;
println(color); // Output: 1
}
9. Generics
Define generic structs and functions with type parameters:
import "std/io";
struct Box<T> {
public:
value: T;
Box(value: T) {
this.value = value;
}
fun get(&this) -> &const T {
return &(this.value);
}
}
fun main() {
let int_box = new Box<int64>(42);
let val = int_box.get();
println(*val); // Output: 42
}
Type parameters must be explicitly specified at call sites.
10. Memory Management and Ownership
Atlas77 uses an ownership system for safe memory management:
import "std/io";
struct Resource {
public:
id: int64;
Resource(id: int64) {
this.id = id;
println("Resource created");
}
~Resource() {
println("Resource destroyed");
}
}
fun main() {
let r = new Resource(1);
// Resource automatically destroyed at end of scope
}
// Output:
// Resource created
// Resource destroyed
Key Points:
- Use
newto allocate memory - Memory is automatically freed at end of scope (RAII)
- Values are moved by default (source becomes invalid)
- Values can be copied if they have a
_copymethod
See Memory Model for complete details.
11. Error Handling
Atlas77 provides optional<T> and expected<T, E> for explicit error handling:
import "std/io";
import "std/optional";
fun divide(a: int64, b: int64) -> optional<int64> {
if b == 0 {
return optional<int64>::empty();
}
return optional<int64>::of(a / b);
}
fun main() {
let result = divide(10, 2);
if result.has_value() {
println(result.value()); // Output: 5
} else {
println("Division by zero!");
}
}
See Error Handling for comprehensive examples.
12. The Standard Library
Atlas77 includes a standard library with essential functionality:
import "std/io"; // Input/output
import "std/fs"; // File system
import "std/string"; // String utilities
import "std/vector"; // Dynamic arrays
import "std/optional"; // Optional values
import "std/expected"; // Error handling
import "std/math"; // Math functions
import "std/time"; // Time operations
fun main() {
println("Hello, World!");
}
Check out the Standard Library for complete documentation.
Next Steps
Now that you understand the basics, explore these topics:
- Hello, World! – Detailed walkthrough of your first program
- Guessing Game – Build a fun interactive program
- Language Reference – Complete language syntax
- Memory Model – Understand ownership and copy semantics
- Error Handling – Master optional and expected types
- Standard Library – Explore available modules
Current Limitations
Atlas77 is actively developed. Some features are not yet available:
- ❌ Multi-line comments
- ❌ For-loops (range-based iteration)
- ❌ Pattern matching
- ❌ Break/continue statements
- ❌ First-class functions and closures
- ❌ Async/await
- ❌ Traits/interfaces
These features are planned for future releases. Check the Roadmap for more details.
Installation
Prerequisites
- Rust Compiler
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
NB: This is only for Unix-based systems (or WSL for Windows). For Windows, you can use Scoop
scoop install main/rust
Or directly from their website: Rust
Installation
Once Rust is installed, you can install Atlas77 using Cargo.
- Install it from Cargo
cargo install atlas_77 - Use it as a CLI
atlas_77 --help - Enjoy!
Or you can clone the repository and build it yourself.
- Clone the repository
git clone https://github.com/atlas77-lang/atlas77.git && cd atlas77 - Build it
cargo build --release - Use it as a CLI
./target/release/atlas_77 --help
Hello, World!
Programming a Guessing Game in Atlas77
Setting up
First create a new Atlas77 project:
atlas77 init guessing_game
cd guessing_game
Then open the src/main.atlas file in your favorite text editor.
Writing the Game Code
Language Reference
This document provides a comprehensive reference for Atlas 77 syntax, semantics, and language features.
Syntax Overview
Comments
Atlas 77 supports single-line comments only:
// This is a single-line comment
let x: int64 = 42; // Comments can appear at end of line
Note
Multi-line comments (
/* ... */) are not yet implemented.
Keywords
Reserved keywords in Atlas 77 include: let, const, fun, struct, import, return, if, else, while, for, new, delete, public, private, this, and others.
Refer to Reserved Keywords for the complete list.
Variables and Constants
Variables are declared with let and constants with const. Type annotations are mandatory for constants, while the compiler can infer types for mutable variables. It’s mandatory for the sake of readability.
let x: int64 = 42;
let name: string = "Atlas";
const PI: float64 = 3.14159;
// Type inference is limited; explicit types are required
let result = 10; // Types can be inferred for initialized variables.
Types
Atlas 77 is statically typed and strongly typed. Here are the fundamental types:
Primitive Types
| Type | Description | Size |
|---|---|---|
int8 | Signed 8-bit integer | 1 byte |
int16 | Signed 16-bit integer | 2 bytes |
int32 | Signed 32-bit integer | 4 bytes |
int64 | Signed 64-bit integer | 8 bytes |
uint8 | Unsigned 8-bit integer | 1 byte |
uint16 | Unsigned 16-bit integer | 2 bytes |
uint32 | Unsigned 32-bit integer | 4 bytes |
uint64 | Unsigned 64-bit integer | 8 bytes |
float32 | 32-bit floating point | 4 bytes |
float64 | 64-bit floating point | 8 bytes |
bool | Boolean (true / false) | 1 byte |
char | Single Unicode character | 4 bytes |
string | UTF-8 string (length in bytes) | Variable |
unit | Empty type (like void) | — |
Note
Some primitive types (like
int8,int16,float32, etc.) are planned but not yet fully implemented.
Strings
Strings are UTF-8 encoded and their length is measured in bytes, not characters:
let greeting: string = "Hello, Atlas!";
Note
String type is currently temporary and may change in future versions.
Arrays
Arrays have fixed length known at compile-time:
let numbers: [int64] = [1, 2, 3, 4, 5];
let items: [string] = ["apple", "banana", "cherry"];
Generics
Atlas 77 supports parametric types (generics) with explicit type parameters (no type inference):
Warning
As of v0.7.0, type inference for generics is not supported. Type parameters must be explicitly specified.
Additionally, generic parameters name can only be a single uppercase letter (e.g.,
T,U,V). This is only temporary and will be relaxed in future versions.
struct Box<T> {
public:
value: T;
Box(val: T) {
this.value = val;
}
}
fun get_value<T>(box: Box<T>) -> T {
return box.value;
}
let int_box: Box<int64> = new Box<int64>(42);
let value: int64 = get_value<int64>(int_box);
Type parameters must be explicitly specified at call sites. For more details, see Generics.
Functions
Functions are declared with fun keyword and require explicit return types:
fun add(a: int64, b: int64) -> int64 {
return a + b;
}
// Function returning nothing (unit) doesn't require to specify return type
fun greet(name: string) {
println("Hello, " + name);
}
// Functions that return nothing use return type 'unit'
fun perform_action() -> unit {
// No return statement needed for unit
}
Return Type Specification
Return types are mandatory for all functions. Use unit for functions that don’t return a value.
Operators and Precedence
Atlas 77 uses operator precedence similar to C/C++.
Operator Precedence Table
Listed from highest to lowest precedence:
| Precedence | Operator | Associativity | Description |
|---|---|---|---|
| 1 | () [] . | Left-to-right | Function call, array index, member access |
| 2 | ! - + ~ | Right-to-left | Logical NOT, unary minus, unary plus, bitwise NOT |
| 3 | * / % | Left-to-right | Multiplication, division, modulo |
| 4 | + - | Left-to-right | Addition, subtraction |
| 5 | << >> | Left-to-right | Bitwise shift left, shift right |
| 6 | < <= > >= | Left-to-right | Relational operators |
| 7 | == != | Left-to-right | Equality operators |
| 8 | & | Left-to-right | Bitwise AND |
| 9 | ^ | Left-to-right | Bitwise XOR |
| 10 | | | Left-to-right | Bitwise OR |
| 11 | && | Left-to-right | Logical AND |
| 12 | || | Left-to-right | Logical OR |
| 13 | = += -= *= /= etc. | Right-to-left | Assignment operators |
Note
Operator overloading is planned for a future version. Some operators have not been implemented yet.
Structs
Structs are product types that group related data:
struct Person {
private:
age: int32;
public:
name: string;
email: string;
Person(name: string, email: string, age: int32) {
this.name = name;
this.email = email;
this.age = age;
}
fun display(this: Person) -> unit {
println(this.name);
}
}
Visibility Rules
By default, all struct fields and methods are private. To make fields or methods public, precede them with a public: block:
struct Config {
private:
internal_state: int64;
public:
api_key: string;
timeout: int32;
}
- Only
api_keyandtimeoutare accessible outside the struct internal_stateis private and only accessible within struct methods
Methods
Methods are functions associated with a struct. The receiver (this) is passed explicitly:
fun display(this) -> unit {
println(this.name);
}
References
References allow you to work with values without taking ownership. References are not nullable.
Warning
Reference design is still a work in progress. There is no guarantee that the current design will remain stable.
Reference Syntax
let x: int64 = 42;
let mutable_ref: &int64 = &x; // Mutable reference
let immutable_ref: &const int64 = &x; // Immutable reference
Reference Behavior
- References are trivially copyable and are copied implicitly
- References cannot be null; all references point to valid values
- The mutability or immutability of the reference is determined at declaration time.
&my_varjust creates a reference and based on the context it is assigned to, it can be mutable or immutable.
Copy and Move Semantics
Atlas77 uses an ownership system where values can be either copied or moved. See Memory Model for comprehensive details.
Copy Semantics (Implicit)
Primitive types, strings, and references are implicitly copyable:
let a: int64 = 10;
let b: int64 = a; // 'a' is copied; both 'a' and 'b' exist
let s1: string = "hello";
let s2: string = s1; // Strings are copyable
let ref_a: &int64 = &a;
let ref_b: &int64 = ref_a; // Reference is copied
Move Semantics (Default for Custom Types)
For custom structs, values are moved by default unless the struct implements a _copy method:
struct Resource {
public:
data: string;
Resource(data: string) {
this.data = data;
}
}
let r1 = new Resource("data");
let r2 = r1; // 'r1' is moved to 'r2'; 'r1' no longer accessible
// Using 'r1' here would be a compile error
Auto-Generated Copy Constructor
The compiler automatically generates a _copy method for structs where the number of fields equals the number of constructor parameters:
struct Point {
public:
x: int64;
y: int64;
Point(x: int64, y: int64) { // 2 params, 2 fields - auto-copy!
this.x = x;
this.y = y;
}
}
let p1 = new Point(10, 20);
let p2 = p1; // Copied automatically
Manual Copy Constructor
To make a custom type copyable or customize copy behavior, implement a _copy method:
struct CopyableData {
public:
value: int64;
CopyableData(val: int64) {
this.value = val;
}
// Manual copy constructor
fun _copy(&const this) -> CopyableData {
return new CopyableData(*(this.value));
}
}
let d1 = new CopyableData(100);
let d2 = d1; // Now 'd1' is copied, both exist
Note: The compiler automatically generates
_copyfor most simple structs. See Memory Model for complete rules.
Memory Management
Atlas 77 uses manual memory management with automatic cleanup at scope boundaries.
Allocation and Deallocation
// Allocate memory with 'new'
let ptr: &MyStruct = &new MyStruct(...);
// Deallocate explicitly with 'delete'
delete ptr;
Automatic Deallocation (RAII)
The compiler automatically inserts delete instructions at the end of each scope for variables that haven’t been moved:
fun example() -> unit {
let resource: MyStruct = MyStruct();
// ... use resource ...
} // Compiler automatically calls delete on 'resource' here
Destructors
When a value is deleted, its destructor is called before memory is freed. Destructors clean up resources:
struct File {
path: string;
// Destructor: called by delete
fun ~File(this: File) -> unit {
// Close file, cleanup resources
}
}
Note
The current runtime is marked as deprecated. Precise semantics of destructors and scope-based cleanup may evolve.
Error Handling
Atlas 77 provides optional<T> and expected<T, E> types for error handling. There is no implicit error propagation (no try/? operator); handle errors explicitly.
optional Type
The optional<T> type represents a value that may or may not exist:
import "std/optional";
fun find_user(id: int64) -> optional<User> {
if id > 0 {
let user = new User(id);
return optional<User>::of(user);
} else {
return optional<User>::empty();
}
}
// Using optional
let user_opt = find_user(1);
if user_opt.has_value() {
let user = user_opt.value();
println("Found user");
} else {
println("User not found");
}
expected Type
The expected<T, E> type represents either a success value or an error:
import "std/expected";
fun parse_int(s: string) -> expected<int64, string> {
// Try to parse
if is_valid_number(s) {
let number = convert_to_int(s);
return expected<int64, string>::expect(number);
} else {
return expected<int64, string>::unexpected("Failed to parse");
}
}
// Using expected
let result = parse_int("42");
if result.is_expected() {
let number = result.expected_value();
println(number);
} else {
let error = result.unexpected_value();
println(error);
}
Value Extraction with Defaults
import "std/optional";
import "std/expected";
// optional with default
let value = some_optional.value_or(42);
// expected with default
let value = some_expected.expected_value_or(0);
Note:
There is no automatic error propagation syntax (try/?operator) by design to maintain explicit control and avoid “hidden magic” in error handling.See Error Handling for comprehensive examples and best practices.
Modules and Imports
Current Import System
Use the import statement to include modules:
import "std/io";
import "std/fs";
import "std/string";
fun main() -> unit {
println("Hello, Atlas!");
}
Imports use file paths (not yet package-based). The compiler locates module files relative to the standard library.
Warning
Multiple imports of the same module may result in duplication even with guard mechanisms. This is a known limitation of the current system.
Future: Package-Based Imports
Package-based imports and selective imports are planned for after the compiler bootstrap phase. Expected syntax:
// Planned (not yet available):
// import mypackage::io;
// from mypackage import io, fs;
Control Flow
If-Else
if condition {
// ...
} else if other_condition {
// ...
} else {
// ...
}
While Loops
while x < 10 {
x = x + 1;
}
For Loops (Basic)
Basic for loops are currently supported:
for i in 0..10 { // Range iteration is planned
println(i);
}
Note
Range-based for-loops (
for x in range) are planned for a future version.
Planned Features
The following features are actively developed and planned for future releases:
- Operator Overloading – Define custom behavior for operators like
+,-,*, etc. - For-loops over Ranges – Iterate over ranges with syntax like
for i in 0..10 - Pattern Matching – Destructure and match values
- First-class Functions and Closures – Functions as values, anonymous functions, closures
- Async/Await – Asynchronous programming support
- Traits (or “Concepts”) – Define shared behavior across types
- Union Types – Discriminated unions for variant data
- Package System – Dependency management and package organization
- Package Manager – Tool for downloading and managing packages
- Copy & Move Semantics Refinement – Better documentation and ergonomics
- Dead Code Elimination – Optimization pass to remove unused code
- Cranelift Backend – Native code generation (in addition to VM execution)
- Compiler Error Recovery – Better error messages and recovery from multiple errors
Known Limitations and Footguns
- No multiline comments – Only single-line comments with
// - No pattern matching – Cannot destructure values into patterns
- No break/continue – Loop control statements not yet implemented
- No variadics – Variable argument functions not supported
- No named parameters – All parameters are positional
- No inheritance – Structs use composition instead of inheritance
- No type inference – All type annotations must be explicit
- String type is temporary – May change in future versions
- stdlib copyability uncertain – Assume standard library types are non-copyable
For detailed examples and tutorials, see:
Memory Model and Ownership
Atlas77 uses an ownership system similar to Rust to manage memory safely and automatically. This document explains how memory allocation, ownership, move semantics, and copy semantics work in Atlas77.
Overview
Key Principles:
- Every variable owns its value
- Ownership can be moved (transferred) or copied (duplicated)
- When a value is moved, the original owner becomes invalid
- When a value is copied, both the original and the copy remain valid
- Automatic memory management through RAII and destructors
Allocation with new
Memory is allocated using the new keyword:
struct Person {
public:
name: string;
age: int64;
Person(name: string, age: int64) {
this.name = name;
this.age = age;
}
}
let person = new Person("Alice", 30);
Automatic Scope-Based Cleanup (RAII)
The compiler automatically inserts delete instructions at the end of each scope for variables that still own their values:
import "std/fs";
fun process_file() -> unit {
let file = new File("data.txt");
// ... use file ...
} // Compiler automatically calls: delete file;
This pattern is called RAII (Resource Acquisition Is Initialization):
- Resources are acquired during object construction (
new) - Resources are released automatically at scope exit via destructors
Example: File Resource Management
import "std/fs";
struct FileHandler {
public:
file: File;
FileHandler(path: string) {
this.file = new File(path);
println("File opened");
}
~FileHandler() {
println("File closed");
delete this.file;
}
}
fun read_and_process() -> unit {
let handler = new FileHandler("input.txt");
// ... use handler ...
} // FileHandler destructor called automatically
Ownership Model
Every variable in Atlas77 owns its value. When a variable goes out of scope, its destructor is automatically called:
fun example() {
let x = new MyStruct(42);
// x owns the MyStruct instance
// ... use x ...
} // x goes out of scope - destructor called automatically
Transferring Ownership
When you pass a value to a function or assign it to another variable, ownership can be transferred:
fun consume(obj: MyStruct) {
// obj now owns the value
} // obj destroyed here
fun main() {
let x = new MyStruct(42);
consume(x); // Ownership transferred to consume()
// x is no longer valid here!
}
Copy Eligibility
A type is copyable if and only if:
- It’s a primitive type:
int64,float64,uint64,bool,char - It’s a reference:
&Tor&const T(references are just pointers) - It’s a string: Built-in
stringtype (copyable but needs freeing) - It has auto-generated copy: Structs where the number of fields equals the number of constructor parameters
- It defines a
_copymethod: Custom structs with an explicit copy constructor
Note: Auto-Generated Copy Constructor (v0.7.0)
The compiler automatically generates a_copymethod for structs where#fields == #constructor_args. This is a simple heuristic that works for most cases but can cause issues with complex types. In v0.7.1, this will be replaced with proper copy constructor generation and better semantics.
Primitive Types
Primitives are always copied - they’re too cheap to move:
let a: int64 = 42;
let b: int64 = a; // Copy (a is still valid)
let c: int64 = a; // Another copy (a still valid)
println(a); // ✓ Works - a is still valid
References
References are lightweight pointers that don’t own the data:
fun borrow(obj: &const MyStruct) {
// obj is just a reference - doesn't own the data
println(obj.value);
}
fun main() {
let x = new MyStruct(42);
borrow(&x); // Pass reference
println(x.value); // ✓ x is still valid
}
Strings
Strings are copyable but still need memory management:
let s1 = "hello";
let s2 = s1; // Copy (both strings valid)
println(s1); // ✓ Works
println(s2); // ✓ Works
// Both destructors will be called
Auto-Generated Copy (Most Structs)
Most simple structs automatically get a copy constructor:
struct Point {
public:
x: int64;
y: int64;
Point(x: int64, y: int64) { // 2 params
this.x = x;
this.y = y;
}
// Compiler auto-generates _copy because: 2 fields == 2 params ✓
}
fun main() {
let p1 = new Point(10, 20);
let p2 = p1; // Copy (auto-generated)
println(p1.x); // ✓ Works
println(p2.x); // ✓ Works
}
Non-Copyable Types
A type is non-copyable when the constructor parameters don’t match the field count:
struct Resource {
public:
id: int64;
handle: int64; // 2 fields
Resource(id: int64) { // 1 param - NO auto-copy generated!
this.id = id;
this.handle = allocate_handle(id); // Computed field
println("Resource acquired");
}
~Resource() {
println("Resource released");
}
// No _copy method - this type is NOT copyable
}
fun main() {
let r1 = new Resource(1);
let r2 = r1; // MOVE (r1 becomes invalid)
// println(r1.id); // ✗ ERROR: r1 was moved
println(r2.id); // ✓ Works
} // Only r2's destructor is called
Manual Copy Constructors
You can always define _copy manually for full control:
struct CustomCopy {
public:
value: int64;
CustomCopy(value: int64) {
this.value = value;
}
// Manual copy constructor
fun _copy(&const this) -> CustomCopy {
println("Custom copy!");
let result = new CustomCopy(*(this.value) * 2); // Custom logic
return result;
}
}
fun main() {
let c1 = new CustomCopy(10);
let c2 = c1; // Uses manual _copy
println(c1.value); // 10
println(c2.value); // 20 (custom logic applied)
}
Move Semantics
Move transfers ownership from one variable to another. The source becomes invalid:
struct Resource {
public:
id: int64;
Resource(id: int64) {
this.id = id;
println("Resource acquired");
}
~Resource() {
println("Resource released");
}
}
fun main() {
let r1 = new Resource(1);
let r2 = r1; // MOVE (r1 becomes invalid)
// println(r1.id); // ✗ ERROR: r1 was moved
println(r2.id); // ✓ Works
} // Only r2's destructor is called
When Moves Happen
Moves occur when:
- A non-copyable value is used (passed to function, assigned, returned)
- A copyable value is used for the last time (optimization)
Copy Semantics
Copy creates a new independent value via the _copy method:
struct Data {
public:
value: int64;
Data(value: int64) {
this.value = value;
}
fun _copy(&const this) -> Data {
println("Copying!");
let result = new Data(*(this.value));
return result;
}
}
fun main() {
let d1 = new Data(100);
let d2 = d1; // COPY (both valid)
d2.value = 200;
println(d1.value); // Prints: 100
println(d2.value); // Prints: 200
} // Both d1 and d2 destructors are called
Copy vs Move for Copyable Types
Even copyable types can be moved if it’s the last use (optimization):
fun process(data: Data) {
println(data.value);
}
fun main() {
let d = new Data(42);
process(d); // MOVE (last use - no copy needed!)
// d is invalid here
}
But if you use it again, it will copy:
fun main() {
let d = new Data(42);
process(d); // COPY (not last use)
println(d.value); // ✓ d is still valid
} // d's destructor called here
}
Common Patterns
Pattern 1: Borrowing for Reads
fun print_value(obj: &const MyStruct) {
println(obj.value);
}
fun main() {
let obj = new MyStruct(42);
print_value(&obj); // Borrow
print_value(&obj); // Borrow again
// obj still valid
}
Pattern 2: Mutable Borrowing
fun modify(obj: &MyStruct) {
obj.value = obj.value + 1;
}
fun main() {
let obj = new MyStruct(42);
modify(&obj); // Mutable borrow
println(obj.value); // 43
}
Pattern 3: Return Values Transfer Ownership
import "std/string";
fun create_message(text: string) -> String {
let msg = new String(text);
return msg; // Ownership transferred to caller
}
fun main() {
let my_msg = create_message("Test");
println(my_msg.s); // ✓ Works
}
Common Pitfalls
Pitfall 1: Use After Move
fun consume(obj: Resource) { }
fun main() {
let r = new Resource(1);
consume(r); // r moved
println(r.id); // ✗ ERROR: use after move
}
Solution: Use references if you need to keep the value:
fun consume(obj: &Resource) { }
fun main() {
let r = new Resource(1);
consume(&r); // Borrow
println(r.id); // ✓ Works
}
Pitfall 2: Mismatched Constructor Parameters
struct MyData {
public:
value: int64;
cached: bool; // 2 fields
MyData(value: int64) { // 1 param - NO auto-copy!
this.value = value;
this.cached = false;
}
}
fun main() {
let d = new MyData(42);
let d2 = d; // MOVE (no auto-copy because 1 param ≠ 2 fields)
println(d.value); // ✗ ERROR: d was moved
}
Solution: Either match parameters to fields, or implement _copy manually:
// Option 1: Match constructor to fields
struct MyData {
public:
value: int64;
cached: bool;
MyData(value: int64, cached: bool) { // Now 2 params = 2 fields ✓
this.value = value;
this.cached = cached;
}
// Auto-copy works now!
}
// Option 2: Implement _copy manually
struct MyData {
public:
value: int64;
cached: bool;
MyData(value: int64) {
this.value = value;
this.cached = false;
}
fun _copy(&const this) -> MyData {
let result = new MyData(*(this.value));
result.cached = this.cached;
return result;
}
}
Pitfall 3: Double Move
fun process(r: Resource) { }
fun main() {
let r = new Resource(1);
process(r); // r moved
process(r); // ✗ ERROR: r already moved
}
Solution: Create a new resource or use references:
fun process(r: &Resource) { }
fun main() {
let r = new Resource(1);
process(&r); // Borrow
process(&r); // ✓ Borrow again
}
Summary
- Ownership: Every value has exactly one owner
- Move: Transfers ownership (source becomes invalid)
- Copy: Creates duplicate (both remain valid, requires
_copymethod) - Primitives: Always copied
- Strings: Copyable but still needs memory management
- Auto-generated copy: Structs where
#fields == #constructor_params - References: Lightweight borrowing without ownership transfer
- Automatic cleanup: Destructors called when variables go out of scope
- Compiler optimization: Last-use moves even for copyable types
The ownership system ensures memory safety while providing explicit control over when values are copied vs moved.
Without the Copy constructor, assignment moves instead of copying.
References
References allow borrowing values without transferring ownership. References in Atlas 77 are:
- Not nullable – always point to valid values
- Trivially copyable – copying is implicit and cheap
- Not rebindable – may change in future versions
Reference Syntax
let value: int64 = 42;
// Mutable reference
let mutable_ref: &int64 = &value;
// Immutable reference
let immutable_ref: &const int64 = &const value;
Using References
fun modify_value(ref: &int64) -> unit {
*ref = 100; // Dereference and modify
}
let x: int64 = 42;
modify_value(&x);
println(x); // 100
Warning
Reference design is still evolving and heavily inspired by Rust. Current behavior and semantics may change.
Destructors
Destructors are special methods that clean up resources when an object is deleted:
struct Resource {
public:
ptr: int64; // Some resource pointer
Resource() {
// Acquire resource
this.ptr = allocate();
}
// Destructor: called by delete
fun ~Resource(this: Resource) -> unit {
if this.ptr != 0 {
deallocate(this.ptr);
this.ptr = 0;
}
}
}
Destructors are called:
- When
deleteis explicitly called - When scope exits (automatic cleanup)
- In the correct order for nested scopes
Destructor Execution Order
For scoped values, destructors are called in reverse order of construction (LIFO):
fun example() -> unit {
let a: Resource = Resource(); // Constructed first
let b: Resource = Resource(); // Constructed second
// ...
} // Destructors called: ~b, then ~a (reverse order)
Scope-Based Cleanup Rules
- Single scope: Variables deleted at end of scope
- Early returns: Variables deleted before returning
- Moved values: Not deleted (ownership transferred)
- Conditional scopes: Variables deleted when block exits
fun conditional_cleanup(flag: bool) -> unit {
let resource1: Resource = Resource();
if flag {
let resource2: Resource = Resource();
// resource2 deleted here
}
// resource1 deleted here
}
Lifetime and Validity
References must remain valid for their entire lifetime:
fun create_ref() -> &int64 {
let x: int64 = 42;
return &x; // ERROR: x will be deleted at end of scope
}
This is a common pitfall. Values cannot outlive their owners:
fun valid_reference(x: &int64) -> &int64 {
return x; // OK: reference comes from parameter, guaranteed to be valid
}
Current Limitations
- Runtime is deprecated – Precise semantics may evolve
- Destructor semantics uncertain – Copy/move interaction with destructors being refined
- stdlib copyability uncertain – Assume standard library types are non-copyable unless documented
Future Improvements
- Smart Pointers (
rc_ptr<T>) – Reference-counted pointers for shared ownership - Guaranteed Copy Constructor Optimization – Automatic elision of unnecessary copies
- Move Semantics Refinement – Clearer rules for implicit copying vs. moving
See Language Reference for details on copy/move semantics.
Error Handling in Atlas 77
Atlas 77 provides explicit error handling through optional<T> and expected<T, E> types. There is no implicit error propagation; errors must be handled explicitly to avoid “hidden magic.”
Philosophy
Error handling in Atlas 77 is explicit and intentional:
- No automatic exception throwing or catching
- No implicit
try/?operator for error propagation - Errors must be checked and handled at the point they occur
- This gives you maximum control over error recovery
optional Type
The optional<T> type represents a value that may or may not exist.
Creating optional Values
Use optional<T> for fallible operations that don’t need error details:
import "std/optional";
struct User {
public:
id: int64;
name: string;
User(id: int64, name: string) {
this.id = id;
this.name = name;
}
}
fun find_user(id: int64) -> optional<User> {
if id > 0 {
// Found user
let user = new User(id, "Alice");
return optional<User>::of(user);
} else {
// Not found
return optional<User>::empty();
}
}
Checking optional Values
let user_opt = find_user(42);
// Check if value exists
if user_opt.has_value() {
let user = user_opt.value(); // Consumes the optional
println(user.name);
} else {
println("User not found");
}
Using value_or for Defaults
import "std/optional";
fun get_config_value(key: string) -> optional<string> {
// ... lookup logic ...
return optional<string>::empty();
}
fun main() {
let timeout = get_config_value("timeout").value_or("30");
println(timeout); // Prints: 30
}
expected Type
The expected<T, E> type represents either a success with a value, or a failure with an error.
Creating expected Values
Use expected<T, E> when operations can fail and you need to communicate why:
import "std/expected";
fun parse_int(s: string) -> expected<int64, string> {
// Try to parse string to integer
if is_valid_number(s) {
let number: int64 = convert_to_int(s);
return expected<int64, string>::expect(number);
} else {
return expected<int64, string>::unexpected("Invalid number format");
}
}
Checking expected Values
let result = parse_int("42");
// Explicit check with is_expected
if result.is_expected() {
let number = result.expected_value(); // Consumes the expected
println(number);
} else {
let error = result.unexpected_value(); // Consumes the expected
println(error);
}
Using expected_value_or for Defaults
import "std/expected";
fun divide(a: int64, b: int64) -> expected<int64, string> {
if b == 0 {
return expected<int64, string>::unexpected("Division by zero");
}
return expected<int64, string>::expect(a / b);
}
fun main() {
let result = divide(10, 0).expected_value_or(0);
println(result); // Prints: 0
}
Pattern: Checking Both Success and Error
let result = parse_int("invalid");
if result.is_expected() {
let value = result.expected_value();
println(value);
} else if result.is_unexpected() {
let error = result.unexpected_value();
println(error); // Prints: Invalid number format
}
Panic
For unrecoverable errors, use panic() to abort the program:
import "std/io";
fun critical_operation() {
if invalid_state {
panic("Critical error: invalid state detected!");
}
}
panic() will:
- Print the error message
- Terminate the program immediately
- Bypass normal cleanup (use sparingly)
Pattern Examples
optional Handling
import "std/optional";
struct User {
public:
id: int64;
name: string;
User(id: int64, name: string) {
this.id = id;
this.name = name;
}
}
fun find_user(id: int64) -> optional<User> {
if id > 0 {
let user = new User(id, "Alice");
return optional<User>::of(user);
}
return optional<User>::empty();
}
fun get_user_name(id: int64) -> optional<string> {
let user_opt = find_user(id);
if user_opt.has_value() {
let user = user_opt.value();
return optional<string>::of(user.name);
} else {
return optional<string>::empty();
}
}
// Usage
fun main() {
let name_opt = get_user_name(1);
if name_opt.has_value() {
println(name_opt.value());
} else {
println("User not found");
}
}
expected Chaining
When multiple fallible operations depend on each other:
import "std/expected";
import "std/fs";
fun read_and_parse() -> expected<int64, string> {
// First operation
let file = new File("numbers.txt");
if !file.exists() {
return expected<int64, string>::unexpected("File not found");
}
let content = file.read();
// Second operation (depends on first)
let number = parse_int(content);
if number.is_unexpected() {
return expected<int64, string>::unexpected(number.unexpected_value());
}
// Success
return expected<int64, string>::expect(number.expected_value());
}
Nested expected with Meaningful Errors
import "std/expected";
struct ParseError {
public:
line: int64;
column: int64;
message: string;
ParseError(line: int64, column: int64, message: string) {
this.line = line;
this.column = column;
this.message = message;
}
}
fun parse_config(path: string) -> expected<Config, ParseError> {
// Try to read file
let file = new File(path);
if !file.exists() {
let error = new ParseError(0, 0, "File not found");
return expected<Config, ParseError>::unexpected(error);
}
let content = file.read();
// Try to parse content
let config = parse_config_text(content);
return config;
}
Best Practices
- Check explicitly – Always use
has_value()/is_expected()/is_unexpected()before consuming values - Provide context – Include helpful error information in
expected<T, E> - Fail fast – Return errors early rather than propagating invalid states
- Use optional for simple cases – When you only need to know if something exists
- Use expected for complex cases – When you need to communicate failure reasons
- Avoid panics in libraries – Let caller decide how to handle errors
- Document error cases – Explain what errors an operation can produce
- Remember consuming methods –
value(),expected_value(), andunexpected_value()consume the container
API Reference
optional Methods
optional<T>::of(data: T) -> optional<T>– Create an optional containing a valueoptional<T>::empty() -> optional<T>– Create an empty optionalhas_value(&this) -> bool– Check if a value is presentvalue(this) -> T– Consume and return the value (panics if empty)value_or(this, default: T) -> T– Consume and return the value or default
expected<T, E> Methods
expected<T, E>::expect(data: T) -> expected<T, E>– Create a successful expectedexpected<T, E>::unexpected(error: E) -> expected<T, E>– Create a failed expectedis_expected(&this) -> bool– Check if this holds a valueis_unexpected(&this) -> bool– Check if this holds an errorexpected_value(this) -> T– Consume and return the value (panics if error)unexpected_value(this) -> E– Consume and return the error (panics if value)expected_value_or(this, default: T) -> T– Consume and return the value or defaultunexpected_value_or(this, default: E) -> E– Consume and return the error or default
Future Improvements
In the future, Atlas 77 may introduce:
- Discriminated unions for more flexible error types
- Error traits for better composability
- More ergonomic error propagation operators
However, the core design will remain explicit and visible, avoiding hidden control flow.
See also:
- Language Reference – Error handling in context
- Memory Model – Resource safety and ownership
- Standard Library – Available error types and utilities
Reserved Keywords
List of all of the reserved keywords in the language:
Branching and control flow
if/elsewhile/forbreak/continuereturn
Declarations
public/private(access modifiers for declarations)import(for importing modules/packages)package(for defining packages/modules)fun(for defining functions)let/const(for defining variables)struct(for defining structures)enum(for defining enumerations)class(for defining classes)
Not used yet, but reserved in case of future use.
concept(for defining concepts which are like interfaces)
Not used yet, but reserved because it’s a feature planned for the future
extern(for defining external functions/variables)
Primitives and types
Primitives types are considered to be keywords in Atlas 77, though this might change in the future.
string(for string type)char(for character type)int8/int16/int32/int64(for signed integer types)uint8/uint16/uint32/uint64(for unsigned integer types)float32/float64(for floating-point types)bool(for boolean type)unit(for unit type)true/false(for boolean literals)This/this(for referring to the current instance in classes/structs)
Other keywords
new/delete(for memory management)as(for type casting)comptime(for compile-time evaluation)
Not used yet, but reserved in case of future use.
operator(for operator overloading)
Not used yet, but reserved in case of future use.
Generics
Generics allow you to write flexible, reusable code by parameterizing types. Atlas77 supports generics for both structs and functions.
Overview
Generics enable you to define data structures and functions that work with multiple types while maintaining type safety. Instead of writing separate implementations for each type, you write one generic implementation.
Key Points:
- Generic parameters must be explicitly specified at call sites (no type inference)
- Generic parameter names must be single uppercase letters (e.g.,
T,U,V) - Constraints can be applied to restrict what types can be used
- Currently, only the
std::copyableconstraint is available
Note: The single-letter restriction for generic parameter names is temporary and will be relaxed in future versions.
Generic Structs
Define a struct with type parameters using angle brackets:
import "std/io";
struct Box<T> {
public:
value: T;
Box(value: T) {
this.value = value;
}
fun get(&this) -> &const T {
return &(this.value);
}
fun set(&this, value: T) {
this.value = value;
}
}
fun main() {
// Type parameter must be explicitly specified
let int_box = new Box<int64>(42);
let str_box = new Box<string>("Hello");
println(*int_box.get()); // Output: 42
println(*str_box.get()); // Output: Hello
}
Multiple Type Parameters
Structs can have multiple generic parameters:
struct Pair<K, V> {
public:
key: K;
value: V;
Pair(key: K, value: V) {
this.key = key;
this.value = value;
}
fun get_key(&const this) -> K {
return *(this.key);
}
fun get_value(&const this) -> V {
return *(this.value);
}
}
fun main() {
let pair = new Pair<int64, string>(1, "one");
println(pair.get_key()); // Output: 1
println(pair.get_value()); // Output: one
}
Generic Functions
Functions can also be generic:
import "std/io";
fun identity<T>(value: T) -> T {
return value;
}
fun swap<T, U>(a: T, b: U) -> Pair<U, T> {
return new Pair<U, T>(b, a);
}
fun main() {
// Explicit type parameters required
let x = identity<int64>(42);
let s = identity<string>("hello");
println(x); // Output: 42
println(s); // Output: hello
}
Calling Generic Functions
Type parameters must be explicitly specified when calling generic functions:
// ✓ Correct - explicit type parameter
let result = identity<int64>(42);
// ✗ Error - type inference not supported
// let result = identity(42);
Generic Constraints
Constraints restrict which types can be used as generic parameters. They ensure that generic code only works with types that support required operations.
The std::copyable Constraint
The std::copyable constraint requires that a type can be copied (has a _copy method or is a primitive type).
Syntax
Apply constraints using a colon (:) after the type parameter name:
struct Container<T: std::copyable> {
public:
data: T;
Container(data: T) {
this.data = data;
}
// This method requires T to be copyable
fun duplicate(&const this) -> T {
return *(this.data); // Copy the data
}
}
Multiple Parameters with Mixed Constraints
You can have some constrained and some unconstrained parameters:
// T and S are copyable, U has no constraint
struct Mixed<T: std::copyable, S: std::copyable, U> {
public:
first: T;
second: S;
third: U;
Mixed(first: T, second: S, third: U) {
this.first = first;
this.second = second;
this.third = third;
}
// Can copy first and second because they're copyable
fun get_first_copy(&const this) -> T {
return *(this.first);
}
fun get_second_copy(&const this) -> S {
return *(this.second);
}
// Can only return reference to third (not copyable)
fun get_third_ref(&const this) -> &const U {
return &(this.third);
}
}
When to Use Constraints
Use std::copyable when your generic code needs to:
- Return values by copy
- Store multiple copies of the same value
- Pass values to functions that consume them (and you need to keep a copy)
import "std/vector";
struct Cache<T: std::copyable> {
private:
items: Vector<T>;
public:
Cache() {
this.items = Vector<T>::with_capacity(10u);
}
fun add(&this, item: T) {
this.items.push(item);
}
// Requires copyable to return a copy
fun get(&this, index: uint64) -> T {
return *(this.items.get(index));
}
}
fun main() {
// Works with copyable types
let cache = new Cache<int64>();
cache.add(42);
let value = cache.get(0u); // Copy returned
println(value);
}
Common Patterns
Generic Container with Methods
import "std/optional";
struct Option<T> {
private:
has_value: bool;
value: T;
public:
Option(has_value: bool, value: T) {
this.has_value = has_value;
this.value = value;
}
fun is_some(&this) -> bool {
return this.has_value;
}
fun unwrap(this) -> T {
if !this.has_value {
panic("Called unwrap on empty Option");
}
return this.value;
}
}
Generic Functions with Constraints
// Only works with copyable types
fun create_pair<T: std::copyable>(value: T) -> Pair<T, T> {
return new Pair<T, T>(value, value);
}
fun main() {
let pair = create_pair<int64>(42);
println(pair.get_key()); // 42
println(pair.get_value()); // 42
}
Generic Wrapper
struct Wrapper<T> {
private:
inner: T;
public:
Wrapper(inner: T) {
this.inner = inner;
}
fun get_ref(&const this) -> &const T {
return &(this.inner);
}
fun get_mut(&this) -> &T {
return &(this.inner);
}
fun consume(this) -> T {
return this.inner;
}
}
Examples from Standard Library
Vector
The standard library’s Vector<T> is a good example of generics in action:
import "std/vector";
fun main() {
// Generic over int64
let numbers = new Vector<int64>([1, 2, 3]);
numbers.push(4);
// Generic over string
let names = new Vector<string>(["Alice", "Bob"]);
names.push("Charlie");
}
optional
The optional<T> type uses generics for nullable values:
import "std/optional";
fun divide(a: int64, b: int64) -> optional<int64> {
if b == 0 {
return optional<int64>::empty();
}
return optional<int64>::of(a / b);
}
expected<T, E>
The expected<T, E> type uses two generic parameters:
import "std/expected";
fun parse_int(s: string) -> expected<int64, string> {
if is_valid(s) {
return expected<int64, string>::expect(parse(s));
}
return expected<int64, string>::unexpected("Invalid number");
}
Current Limitations
- No type inference – Type parameters must always be explicitly specified
- Single-letter names only – Generic parameters must be single uppercase letters (temporary)
- One constraint only – Only
std::copyableis currently available - No default type parameters – Cannot specify default types for generic parameters
- No variance annotations – No covariance or contravariance support
Future Enhancements
Planned improvements for the generics system include:
- Multi-character generic parameter names – Allow names like
Key,Value,Element - More constraints – Additional built-in constraints (e.g.,
std::comparable,std::numeric) - Custom constraints – Define your own constraints (traits/concepts)
- Type inference – Infer type parameters from context where possible
- Default type parameters – Specify default types for optional parameters
- Where clauses – More flexible constraint syntax
Best Practices
-
Use descriptive names – Even with single letters, choose meaningful ones:
Tfor general TypeKfor KeyVfor ValueEfor ErrorRfor Result
-
Apply constraints appropriately – Only add
std::copyablewhen you actually need to copy values -
Document type requirements – Comment on what operations your generic code expects:
// T must support equality comparison (not enforced yet) struct Set<T> { /* ... */ } -
Prefer references when possible – If you don’t need to copy, use references:
fun inspect<T>(value: &const T) { // No copy required } -
Be explicit – Always specify type parameters clearly:
// Good let box = new Box<int64>(42); // Not supported // let box = new Box(42);
See also:
- Language Reference – Complete language syntax
- Standard Library – Generic types in the standard library
- Memory Model – Copy semantics and ownership
Standard Library
Note:
The standard library is a work in progress. Some modules are complete while others are still in development. This documentation reflects the current state of the implemented modules.
Overview
The Atlas77 standard library provides essential data structures, utilities, and I/O functions. The library is organized into modules that can be imported individually.
Each module is now documented on its own page with complete API reference, usage examples, and best practices.
Module Index
Core Utilities
| Module | Description | Status |
|---|---|---|
| std/io | Input/output operations (print, println, input, panic) | ✅ Stable |
| std/string | String manipulation and text processing | ✅ Stable |
| std/mem | Memory management utilities (swap, drop, size_of) | ✅ Stable |
Collections
| Module | Description | Status |
|---|---|---|
| std/vector | Dynamic arrays with Vector | ✅ Stable |
| std/map | Hash maps for key-value storage with Map<K,V> | ✅ Stable |
| std/queue | FIFO queue data structure with Queue | ✅ Stable |
| std/iter | Iterator utilities with Iter | ✅ Stable |
Error Handling
| Module | Description | Status |
|---|---|---|
| std/optional | Nullable values with optional | ✅ Stable |
| std/expected | Result types for error handling with expected<T,E> | ✅ Stable |
File System
| Module | Description | Status |
|---|---|---|
| std/fs | File operations (read, write, exists, remove) | ✅ Stable |
Math & Time
| Module | Description | Status |
|---|---|---|
| std/math | Mathematical functions (abs, min, max, pow, trigonometry, random) | ✅ Stable |
| std/time | Time operations and formatting | ✅ Stable |
Deprecated Modules
| Module | Description | Status |
|---|---|---|
| std/box | Heap-allocated values - use direct allocation instead | ⚠️ Deprecated |
std/io
Basic input/output and program control functions.
Functions
print<T>(s: T)
Print a value to standard output without a newline.
import "std/io";
print("Hello");
print(" World!");
// Output: Hello World!
println<T>(s: T)
Print a value to standard output followed by a newline.
import "std/io";
println("Hello, Atlas!");
// Output: Hello, Atlas!
// (newline)
input() -> string
Read a line from standard input.
import "std/io";
println("Enter your name:");
let name = input();
println("Hello, " + name);
panic<T>(s: T)
Abort the program with an error message.
import "std/io";
fun divide(a: int64, b: int64) -> int64 {
if b == 0 {
panic("Division by zero!");
}
return a / b;
}
std/fs
File system operations for reading, writing, and managing files.
Struct: File
Represents a file in the file system.
struct File {
private:
content: string;
public:
path: string;
}
Constructor
import "std/fs";
let file = new File("data.txt");
Creates a File object. The file is not opened until read() or open() is called.
Methods
read(&this) -> string
Read the entire content of the file and store it internally.
let file = new File("data.txt");
let content = file.read();
println(content);
open(&this)
Open and read the file, storing content internally.
let file = new File("data.txt");
file.open();
// File content is now loaded
write(&this, content: string)
Write content to the file.
let file = new File("output.txt");
file.write("Hello, World!");
close(this)
Close the file (consumes the File object).
let file = new File("data.txt");
file.read();
file.close(); // File is consumed
exists(&this) -> bool
Check if the file exists.
let file = new File("config.json");
if file.exists() {
println("File found");
}
remove(&this)
Delete the file from the file system.
let file = new File("temp.txt");
file.remove();
read_dir(&this, path: string) -> [string]
Read the contents of a directory (instance method).
let file = new File(".");
let entries = file.read_dir("./src");
read_file(&this, path: string) -> string
Read a file without modifying the File instance’s content.
let file = new File("dummy");
let content = file.read_file("actual.txt");
std/string
String manipulation functions and the String struct.
External Functions
These functions work with the primitive string type.
str_len(s: &const string) -> uint64
Get the length of a string primitive (in bytes).
import "std/string";
let text = "Hello";
let length = str_len(&text); // 5
trim(s: string) -> string
Remove leading and trailing whitespace.
let text = " hello ";
let trimmed = trim(text); // "hello"
to_upper(s: string) -> string
Convert a string to uppercase.
let text = "hello";
let upper = to_upper(text); // "HELLO"
to_lower(s: string) -> string
Convert a string to lowercase.
let text = "HELLO";
let lower = to_lower(text); // "hello"
split(s: &string, sep: &string) -> [string]
Split a string by a separator.
let text = "apple,banana,cherry";
let parts = split(&text, &",");
// ["apple", "banana", "cherry"]
str_cmp(s1: &const string, s2: &const string) -> uint64
Compare two strings lexicographically.
let result = str_cmp(&"apple", &"banana");
to_chars(s: &const string) -> [char]
Convert a string to an array of characters.
let text = "Hi";
let chars = to_chars(&text); // ['H', 'i']
from_chars(s: [char]) -> string
Convert an array of characters to a string.
let chars = ['H', 'i'];
let text = from_chars(chars); // "Hi"
Struct: String
A struct wrapper around the primitive string type with additional methods.
struct String {
public:
s: string;
len: uint64;
}
Constructor
import "std/string";
let str = new String("Hello");
Static Methods
String::from_chars(s: [char]) -> String
Create a String from an array of characters.
let chars = ['H', 'i'];
let str = String::from_chars(chars);
String::str_len(s: &const string) -> uint64
Get the length of a string primitive.
let length = String::str_len(&"Hello"); // 5
Instance Methods
len(&this) -> uint64
Get the length of the String.
let str = new String("Hello");
println(str.len()); // 5
is_empty(&this) -> bool
Check if the String is empty.
let str = new String("");
if str.is_empty() {
println("String is empty");
}
concat(&this, other: String) -> String
Concatenate two Strings.
let str1 = new String("Hello");
let str2 = new String(" World");
let result = str1.concat(str2);
push(&this, c: char)
Append a character to the String.
let str = new String("Hello");
str.push('!');
push_str(&this, s: String)
Append another String.
let str1 = new String("Hello");
let str2 = new String(" World");
str1.push_str(str2);
get(&this, index: uint64) -> char
Get a character at a specific index.
let str = new String("Hello");
let ch = str.get(0u); // 'H'
set(&this, index: uint64, c: char)
Set a character at a specific index.
let str = new String("Hello");
str.set(0u, 'h'); // "hello"
to_str(&this) -> string
Convert the String struct to a string primitive.
let str = new String("Hello");
let s = str.to_str();
to_chars(&this) -> [char]
Convert the String to an array of characters.
let str = new String("Hi");
let chars = str.to_chars();
to_upper(&this) -> String
Convert to uppercase.
let str = new String("hello");
let upper = str.to_upper(); // "HELLO"
to_lower(&this) -> String
Convert to lowercase.
let str = new String("HELLO");
let lower = str.to_lower(); // "hello"
trim(&this) -> String
Remove leading and trailing whitespace.
let str = new String(" hello ");
let trimmed = str.trim(); // "hello"
split(&this, sep: string) -> [String]
Split the String by a separator.
let str = new String("a,b,c");
let parts = str.split(",");
into_iter(this) -> Iter<char>
Consume the String and create an iterator over its characters.
let str = new String("Hi");
let iter = str.into_iter();
std/vector
Dynamic arrays with resizable capacity.
External Functions
len<T>(data: &const [T]) -> uint64
Get the length of an array.
import "std/vector";
let arr = [1, 2, 3];
let length = len(&arr); // 3
slice<T>(data: &const [T], start: uint64, end: uint64) -> [T]
Extract a slice from an array.
let arr = [1, 2, 3, 4, 5];
let sub = slice(&arr, 1u, 4u); // [2, 3, 4]
Struct: Vector<T>
A generic dynamic array that automatically resizes as elements are added.
struct Vector<T> {
private:
data: [T];
public:
length: uint64;
capacity: uint64;
}
Constructor
import "std/vector";
let vec = new Vector<int64>([1, 2, 3]);
Creates a vector from an array, setting both length and capacity to the array’s length.
Destructor
The vector’s destructor automatically cleans up all elements within data[0..length] and frees the underlying array.
Static Methods
Vector<T>::with_capacity(capacity: uint64) -> Vector<T>
Create a vector with a specific initial capacity.
let vec = Vector<int64>::with_capacity(10u);
The vector starts with length 0 but has pre-allocated space for 10 elements.
Instance Methods
len(&const this) -> uint64
Get the number of elements in the vector.
println(vec.len());
get(&const this, index: uint64) -> &const T
Get a const reference to an element.
let vec = new Vector<int64>([1, 2, 3]);
let val = vec.get(1u); // &2
Panics if index is out of bounds.
get_mut(&this, index: uint64) -> &T
Get a mutable reference to an element.
let vec = new Vector<int64>([1, 2, 3]);
let val = vec.get_mut(1u);
*val = 10;
Panics if index is out of bounds.
set(&this, index: uint64, val: T)
Set an element at a specific index.
vec.set(1u, 10);
Panics if index is out of bounds.
push(&this, val: T)
Add an element to the end of the vector.
vec.push(42);
Automatically resizes the vector if capacity is reached (doubles the capacity).
pop(&this) -> T
Remove and return the last element.
let last = vec.pop();
Panics if the vector is empty.
take(&this, index: uint64) -> T
Takes an element out of the vector at the specified index, shifting subsequent elements to the left.
let item = vec.take(2u); // Removes element at index 2
Panics if index is out of bounds.
get_slice(&this, start: uint64, end: uint64) -> [T]
Get a slice of the vector as an array.
let slice = vec.get_slice(1u, 4u);
Panics if indices are out of bounds or if start > end.
into_iter(this) -> Iter<T>
Consume the vector and create an iterator.
let vec = new Vector<int64>([1, 2, 3]);
let iter = vec.into_iter();
The vector cannot be used after calling this method.
std/map
Key-value hash map for associating keys with values.
Struct: Map<K, V>
Generic hash map that associates keys of type K with values of type V.
struct Map<K, V> {
private:
keys: Vector<K>;
values: Vector<V>;
}
Constructor
import "std/map";
let map = new Map<string, int64>();
Creates an empty map.
Instance Methods
insert(&this, key: K, value: V)
Insert or update a key-value pair.
let map = new Map<string, int64>();
map.insert("age", 25);
map.insert("score", 100);
map.insert("age", 26); // Updates existing value
get(&const this, key: K) -> optional<&const V>
Get a const reference to the value for a key.
let map = new Map<string, int64>();
map.insert("age", 25);
let age_opt = map.get("age");
if age_opt.has_value() {
let age_ref = age_opt.value();
println(*age_ref); // 25
}
Returns optional::empty() if the key doesn’t exist.
get_mut(&this, key: K) -> optional<& V>
Get a mutable reference to the value for a key.
let map = new Map<string, int64>();
map.insert("score", 100);
let score_opt = map.get_mut("score");
if score_opt.has_value() {
let score_ref = score_opt.value();
*score_ref = *score_ref + 10; // Increment by 10
}
let final_score = map.get("score").value();
println(*final_score); // 110
remove(&this, key: K) -> optional<V>
Remove a key-value pair, returning the value if it existed.
let map = new Map<string, int64>();
map.insert("age", 25);
let removed = map.remove("age");
if removed.has_value() {
println("Removed: " + removed.value());
}
let not_found = map.remove("age"); // Returns optional::empty()
contains(&this, key: K) -> bool
Check if a key exists.
let map = new Map<string, int64>();
map.insert("age", 25);
if map.contains("age") {
println("Age is present");
}
if !map.contains("name") {
println("Name is not present");
}
length(&this) -> uint64
Get the number of key-value pairs.
let map = new Map<string, int64>();
map.insert("a", 1);
map.insert("b", 2);
println(map.length()); // 2
is_empty(&this) -> bool
Check if the map is empty.
let map = new Map<string, int64>();
println(map.is_empty()); // true
map.insert("key", 42);
println(map.is_empty()); // false
clear(&this)
Remove all key-value pairs.
let map = new Map<string, int64>();
map.insert("a", 1);
map.insert("b", 2);
map.clear();
println(map.is_empty()); // true
std/queue
Queue (FIFO) data structure for ordered element processing.
Struct: Queue<T>
A simple FIFO (first-in, first-out) queue data structure.
struct Queue<T> {
private:
items: [T];
head: uint64;
tail: uint64;
size: uint64;
count: uint64;
}
Constructor
import "std/queue";
let queue = new Queue<T>(items);
Creates a queue from an existing array.
Static Methods
Queue<T>::with_capacity(size: uint64) -> Queue<T>
Instantiates a queue with a predefined capacity.
let queue = Queue<int64>::with_capacity(10u);
Queue<T>::from_vector(vec: Vector<T>) -> Queue<T>
Creates a queue from a vector.
let vec = new Vector<int64>([1, 2, 3]);
let queue = Queue<int64>::from_vector(vec);
Instance Methods
enqueue(&this, item: T) -> optional<unit>
Enqueues an item to the back of the queue.
let result = queue.enqueue(42);
if result.is_empty() {
println("Queue is full");
}
Returns optional<unit>::empty() if the queue is full.
enqueue_front(&this, item: T) -> optional<unit>
Enqueues an item to the front of the queue.
let result = queue.enqueue_front(42);
Returns optional<unit>::empty() if the queue is full.
dequeue(&this) -> optional<T>
Dequeues an item from the front of the queue.
let item = queue.dequeue();
if item.has_value() {
println(item.value());
}
Returns optional<T>::empty() if the queue is empty.
dequeue_back(&this) -> optional<T>
Dequeues an item from the back of the queue.
let item = queue.dequeue_back();
Returns optional<T>::empty() if the queue is empty.
peek(&const this) -> optional<&const T>
View the front item without removing it.
let front = queue.peek();
if front.has_value() {
println(*front.value());
}
peek_back(&const this) -> optional<&const T>
View the back item without removing it.
let back = queue.peek_back();
is_full(&const this) -> bool
Check if the queue is at capacity.
if queue.is_full() {
println("Queue is full");
}
is_empty(&const this) -> bool
Check if the queue is empty.
if queue.is_empty() {
println("Queue is empty");
}
len(&const this) -> uint64
Get the number of items in the queue.
println("Queue has " + queue.len() + " items");
to_vector(this) -> Vector<T>
Consumes the queue and returns a vector containing all items.
let vec = queue.to_vector();
std/iter
Iterator utilities for traversing collections.
Struct: Iter<T>
Generic iterator over elements of type T.
struct Iter<T> {
public:
data: Vector<T>;
index: uint64;
}
Warning
Iter<T>uses aVector<T>under the hood and every.next()calls use theVector<T>.take(i)method, which moves outTthen collapses the vector to not have empty holes. It’s very inefficient for long iterator.Iter<T>should be reworked to use[T]under the hood and manage it by itself.
Constructor
import "std/iter";
import "std/vector";
let vec = new Vector<int64>([1, 2, 3]);
let iter = new Iter<int64>(vec);
Copy Constructor
Note
Most copy constructors need
Tto be copyable, it is not yet properly enforced by the compiler. Currently you will get a weird error akin to “use of moved value” somewhere in the std lib code.
import "std/iter";
import "std/vector";
let vec = new Vector<int64>([1, 2, 3]);
let iter = new Iter<int64>(vec);
let iter_copy = new Iter<int64>(&iter);
Static Methods
Iter<T>::from_array(data: [T]) -> Iter<T>
Create an iterator from an array.
let arr = [1, 2, 3];
let iter = Iter<int64>::from_array(arr);
Iter<char>::from_string(data: String) -> Iter<char>
Create an iterator from a String.
import "std/string";
let str = new String("Hi");
let iter = Iter<char>::from_string(str);
Iter<char>::from_str(data: &const string) -> Iter<char>
Create an iterator from a string primitive.
let text = "Hi";
let iter = Iter<char>::from_str(&text);
Instance Methods
next(&this) -> optional<T>
Get the next item (returns empty if exhausted).
let iter = Iter<int64>::from_array([1, 2]);
let first = iter.next(); // optional::of(1)
let second = iter.next(); // optional::of(2)
let third = iter.next(); // optional::empty()
peek(&this) -> optional<&const T>
Peek at the next item without advancing.
let iter = Iter<int64>::from_array([1, 2]);
let peeked = iter.peek(); // optional::of(&1)
let next = iter.next(); // optional::of(1)
has_next(&this) -> bool
Check if there are more items.
while iter.has_next() {
let item = iter.next().value();
println(item);
}
std/optional
Type-safe nullable values.
Struct: optional<T>
A lightweight container for an optional value of type T.
union optional_storage<T> {
value: T;
empty: unit;
}
struct optional<T> {
private:
data: optional_storage<T>;
has_value: bool;
public:
~optional();
fun _copy(&const this) -> optional<T>;
}
When has_value is true, data.value is valid and will be dropped by the destructor.
Static Methods
optional<T>::of(data: T) -> optional<T>
Construct an optional<T> containing data.
import "std/optional";
let some = optional<int64>::of(42);
optional<T>::empty() -> optional<T>
Construct an empty optional<T> with no value.
let none = optional<int64>::empty();
Instance Methods
has_value(&const this) -> bool
Returns true if a value is present.
if opt.has_value() {
println("has value");
}
is_empty(&const this) -> bool
Returns true if no value is present.
if opt.is_empty() {
println("is empty");
}
value(this) -> T
Consume the optional and return the contained value.
let opt = optional<int64>::of(42);
let val = opt.value(); // 42
Panics with a descriptive message when
has_valueis false. Consider usingvalue_or(default)to avoid panics.
value_or(this, default: T) -> T
Consume the optional and return the contained value or default if empty.
let opt = optional<int64>::empty();
let n = opt.value_or(0); // 0
std/expected
Result type for operations that can fail with error information.
Struct: expected<T, E>
Represents either a success value or an error.
union expected_storage<T, E> {
expected_value: T;
unexpected_value: E;
}
struct expected<T, E> {
private:
data: expected_storage<T, E>;
has_expected_value: bool;
}
Static Methods
expected<T, E>::expect(data: T) -> expected<T, E>
Create a successful expected.
import "std/expected";
let result = expected<int64, string>::expect(42);
expected<T, E>::unexpected(error: E) -> expected<T, E>
Create a failed expected.
let result = expected<int64, string>::unexpected("Error occurred");
Instance Methods
is_expected(&this) -> bool
Check if this contains a value.
let result = expected<int64, string>::expect(42);
if result.is_expected() {
println("Success");
}
is_unexpected(&this) -> bool
Check if this contains an error.
if result.is_unexpected() {
println("Error occurred");
}
expected_value(this) -> T
Consume and return the value (panics if error).
let result = expected<int64, string>::expect(42);
let val = result.expected_value(); // 42
Warning: This method panics if the expected contains an error. Always check with
is_expected()first or useexpected_value_or().
unexpected_value(this) -> E
Consume and return the error (panics if value).
let result = expected<int64, string>::unexpected("Error");
let err = result.unexpected_value(); // "Error"
expected_value_or(this, default: T) -> T
Consume and return the value or a default.
let result = expected<int64, string>::unexpected("Error");
let val = result.expected_value_or(0); // 0
unexpected_value_or(this, default: E) -> E
Consume and return the error or a default.
let result = expected<int64, string>::expect(42);
let err = result.unexpected_value_or("No error"); // "No error"
std/math
Mathematical functions for numerical operations.
Functions
Integer Functions
abs(x: int64) -> int64
Absolute value of an integer.
import "std/math";
let result = abs(-5); // 5
let result2 = abs(10); // 10
min(x: int64, y: int64) -> int64
Minimum of two integers.
let result = min(5, 10); // 5
let result2 = min(-3, 2); // -3
max(x: int64, y: int64) -> int64
Maximum of two integers.
let result = max(5, 10); // 10
let result2 = max(-3, 2); // 2
pow(x: int64, y: int64) -> int64
Raise x to the power of y (integers).
let result = pow(2, 10); // 1024
let result2 = pow(5, 3); // 125
Floating-Point Functions
abs_f(x: float64) -> float64
Absolute value of a float.
let result = abs_f(-3.14); // 3.14
let result2 = abs_f(2.5); // 2.5
min_f(x: float64, y: float64) -> float64
Minimum of two floats.
let result = min_f(3.5, 2.1); // 2.1
let result2 = min_f(-1.5, 0.0); // -1.5
max_f(x: float64, y: float64) -> float64
Maximum of two floats.
let result = max_f(3.5, 2.1); // 3.5
let result2 = max_f(-1.5, 0.0); // 0.0
pow_f(x: float64, y: int64) -> float64
Raise x to the power of y (float base, integer exponent).
let result = pow_f(2.0, 3); // 8.0
let result2 = pow_f(1.5, 2); // 2.25
round(x: float64) -> int64
Round a float to the nearest integer.
let result = round(3.7); // 4
let result2 = round(3.2); // 3
let result3 = round(3.5); // 4
Trigonometric Functions
sin_f(x: float64) -> float64
Sine of x (in radians).
let result = sin_f(0.0); // 0.0
let result2 = sin_f(3.14159 / 2.0); // ~1.0
cos_f(x: float64) -> float64
Cosine of x (in radians).
let result = cos_f(0.0); // 1.0
let result2 = cos_f(3.14159); // ~-1.0
Random Number Generation
random(min: int64, max: int64) -> int64
Generate a random integer in the range [min, max] (inclusive).
let num = random(1, 10); // Random number between 1 and 10
let dice = random(1, 6); // Simulate a dice roll
std/time
Time operations and formatting.
Struct: Time
Represents a point in time or duration.
struct Time {
public:
sec: int64;
nsec: int64;
}
Constructor
import "std/time";
let time = new Time(1234567890, 0);
Creates a Time with the specified seconds and nanoseconds.
Static Methods
Time::now() -> Time
Get the current time.
let now = Time::now();
println(now.sec);
Instance Methods
format(&this, fmt: string) -> string
Format the time as a string using format specifiers.
let time = Time::now();
let formatted = time.format("%Y-%m-%d %H:%M:%S");
println(formatted); // e.g., "2026-01-06 14:30:45"
Format Specifiers:
%Y- Year (e.g., 2026)%m- Month (01-12)%d- Day (01-31)%H- Hour (00-23)%M- Minute (00-59)%S- Second (00-59)
to_iso_string(&this) -> string
Convert to ISO 8601 format.
let time = Time::now();
let iso = time.to_iso_string();
println(iso); // e.g., "2026-01-06T14:30:45"
sleep(&this)
Sleep for the duration represented by this Time.
let duration = new Time(2, 0); // 2 seconds
duration.sleep();
println("2 seconds passed");
Note: This is a blocking sleep operation.
elapsed(&this, since: Time) -> Time
Calculate elapsed time since another time.
let start = Time::now();
// ... do work ...
let end = Time::now();
let duration = end.elapsed(start);
println(duration.sec); // Seconds elapsed
std/mem
Memory management utilities and low-level operations.
Functions
size_of<T>() -> uint64
Note
Not yet implemented.
Get the size in bytes of type T.
import "std/mem";
import "std/io";
fun main() {
println(size_of<int64>()); // 8
println(size_of<float64>()); // 8
println(size_of<bool>()); // 1
println(size_of<char>()); // 1
}
align_of<T>() -> uint64
Note
Not yet implemented.
Get the alignment requirement in bytes of type T.
import "std/mem";
import "std/io";
fun main() {
println(align_of<int64>()); // 8
println(align_of<float64>()); // 8
println(align_of<bool>()); // 1
}
forget<T>(value: T)
Note
Not yet implemented. And it will probably have another name.
Prevent a value from being dropped, leaking its memory.
import "std/mem";
fun main() {
let data = new Vector<int64>([1, 2, 3]);
forget(data); // Memory is leaked!
// data's destructor will never run
}
Warning: This leaks memory! Only use this in very specific scenarios.
swap<T>(a: &T, b: &T)
Note
Not yet implemented.
Swap the values of two variables.
import "std/mem";
import "std/io";
fun main() {
let x = 10;
let y = 20;
swap(&x, &y);
println(x); // 20
println(y); // 10
}
replace<T>(dest: &T, src: T) -> T
Note
Not yet implemented.
Replace the value at dest with src, returning the old value.
import "std/mem";
import "std/io";
fun main() {
let x = 10;
let old = replace(&x, 20);
println(old); // 10
println(x); // 20
}
memcpy<T>(value: &const T) -> T
Create a shallow copy of a value from a reference.
import "std/mem";
import "std/io";
fun main() {
let x = 42;
let y = memcpy(&x);
println(x); // 42
println(y); // 42
}
std/box
Deprecated: The box module is deprecated and should not be used in new code. Use stack allocation or Vector
instead.
Struct: Box<T>
Heap-allocated single value container.
struct Box<T> {
public:
value: T;
}
Constructor
import "std/box";
let boxed = new Box<int64>(42);
Instance Methods
get(&this) -> &const T
Get a const reference to the value.
let boxed = new Box<int64>(42);
let value_ref = boxed.get();
println(*value_ref); // 42
get_mut(&this) -> &T
Get a mutable reference to the value.
let boxed = new Box<int64>(42);
let value_ref = boxed.get_mut();
*value_ref = 100;
println(*boxed.get()); // 100
set(&this, new_value: T)
Set the value.
let boxed = new Box<int64>(42);
boxed.set(100);
println(*boxed.get()); // 100
Blue Engine Library
Blue Engine is the experimental FFI testbed connecting Atlas77 to Rust. It serves as one of the core libraries shipped with Atlas77 and can be imported directly:
import blue_engine::triangle; import blue_engine::Engine; import blue_engine::ObjectSettings; import blue_engine::call_update_function; import std::result; import std::time; fun main() { let engine = Engine::init(); triangle("my_triangle", ObjectSettings::init("default"), &engine.renderer, &engine.objects).expect("Failed to create triangle"); let start = Time::now(); call_update_function(&engine, "update", start); } fun update(delta: Time, engine: &Engine) { return; }
The objective is to replicate the public API and general usage patterns of the original Blue Engine crate while adapting it to Atlas77’s execution model, type system, and VM. This library is under active development. Interfaces may change, features may remain incomplete, and stability is not guaranteed.
Current scope: a safe Atlas77 wrapper layer built entirely on extern_ptr and extern_fn, exposing Rust engine components without unsafe surface area in Atlas77.
Atlas 77 Language Roadmap
Current Version
Atlas 77 is currently in early development (v0.7.1). The compiler and runtime are functional but not production-ready. Expect breaking changes and API modifications.
Atlas77 Roadmap Update
Note
This roadmap is subject to change as development progresses. The focus areas and features listed are planned but may evolve based on community feedback and technical considerations. It also only talks about the future of Atlas77.
v0.7.x Covenant
Already released This is the next major milestone and is actively being worked on.
Focus areas:
- Introduction of move/copy semantics to enable safer code and automatic scope cleanup
- Rework of the
_copyconstructor semantics - Expansion and stabilization of parts of the standard library
- Introduction of
memcpy<T>(&T) -> Tfor explicit shallow copies - Addition of a HIR pretty printer to inspect the output of the compiler after the last HIR pass.
Planned v0.7.x breakdown (subject to change)
To give more visibility into the expected progression of the v0.7 series, the current intent is roughly:
v0.7.0
- Initial release based on the current ownership and move/copy work
- Content largely matching what is present in the current PR: https://github.com/atlas77-lang/atlas77/pull/141
v0.7.1
Already released
- Complete rework of the
_copyconstructor semantics - Introduction of
std::copyableandstd::non_copyableflags on structs to make copy behavior explicit
Similar to how Rust does
#[derive(Copy)]to make a struct copyable. In Atlas77 it will be a bit different,std::copyablewill force a struct to be copyable (if possible) but you don’t have to addstd::copyablefor a struct to be copyable by default, it just forces the compiler to try.std::non_copyablewill prevent a struct from being copyable even if all its fields are copyable.
v0.7.2
Currently in development
- Introduction of constraints on methods of generic structs, based on the generic parameters of the struct
Example:
Vector<T>.foo()may only exist ifTsatisfiesstd::copyable
Other planned v0.7.x features (not strictly tied to a specific sub-release)
- Compiler recover from certain errors and continue processing to provide more complete diagnostics
- Improvements to error messages and diagnostics
v0.8.x
This release will focus on runtime and execution model changes, including:
- Reworking the VM from stack-based to register-based
- Moving toward a typed VM
- Improving the heap with more realistic allocation behavior
v0.7 and v0.8 are the only releases that are currently clearly scoped and planned in this order.
Beyond v0.8.x
Everything after v0.8 is not set in stone, both in scope and in scheduling. These are directions rather than commitments:
- v0.9.x: package system improvements, namespacing (
std::foo::bar), larger std, package manager - v0.10.x: optimization passes (DCE, constant folding, inlining, loop unrolling, etc.), likely introduced incrementally
- v0.11.x: higher-level language features (operator overloading, traits/interfaces, generic methods, constraints)
- v1.0.x: undecided; possibly bootstrapping, possibly something else entirely