A transpiler from Veltrano (Kotlin-like syntax) to Rust with a reference-by-default type system.
- Installation
- Quick Start
- Overview
- Type System
- Language Guide
- Command Line Reference
- Examples
- Design Decisions
# Clone the repository
git clone https://github.com/Portfoligno/veltrano-transpiler.git
cd veltrano-transpiler
# Build the project
cargo build --release
# Run tests
cargo test
# Transpile a Veltrano file to Rust
cargo run examples/hello.vl
# With comment preservation
cargo run -- --preserve-comments examples/hello.vl
# The output will be saved as hello.rs
Example Input (hello.vl):
fun main() {
val message: Str = "Hello, Veltrano!"
println("{}", message)
}
Generated Output (hello.rs):
fn main() {
let message: &str = "Hello, Veltrano!";
println!("{}", message);
}
Veltrano is designed with these core principles:
- Reference-by-default: Borrowing is the common case in Rust, so Veltrano makes it the default
- Explicit ownership: When you need ownership, use
Own<T>
to make it visible and intentional - Kotlin-like syntax: Familiar syntax for Kotlin developers with Rust's powerful semantics
- Immutability-first: Only
val
(immutable bindings) are supported, encouraging functional patterns
- Familiar Syntax: Kotlin-like keywords (
fun
,val
,if
,while
) - Type Safety: Full type annotations and inference
- Automatic Conversions: Systematic identifier conversion (uppercase→underscore+lowercase, underscore→double)
- Comment Preservation: Optional comment preservation in generated code
- Clear Semantics: Direct mapping to idiomatic Rust patterns
In Veltrano, the type system is built around making references the default:
- Types without
Own<>
are references by default - Use
Own<T>
for explicit ownership - Basic types (
I64
,Bool
,Unit
,Nothing
) are always owned (matching Rust's Copy types) - Use
Ref<T>
for additional reference levels - Use
MutRef<T>
for mutable references
Veltrano Type | Rust Type | Description | Example |
---|---|---|---|
I64 |
i64 |
64-bit integer (always owned) | val x: I64 = 42 |
Bool |
bool |
Boolean (always owned) | val flag: Bool = true |
Unit |
() |
Unit type | fun doSomething(): Unit |
Nothing |
! |
Never type | fun abort(): Nothing |
Str |
&str |
String slice reference | val s: Str = "hello" |
String |
&String |
String reference (default) | val s: String = owned.ref() |
Pattern | Applied to Owned Base | Applied to Reference Base |
---|---|---|
Own<T> |
Invalid (Own<I64> ) |
Own<String> → String |
Ref<T> |
Ref<I64> → &i64 |
Ref<String> → &&String |
MutRef<T> |
MutRef<I64> → &mut i64 |
MutRef<String> → &mut &String |
Box<T> |
Box<I64> → &Box<i64> |
Box<Str> → &Box<&str> |
Convert owned values to references:
val owned: Own<String> = "Hello".toString() // String (owned)
val borrowed: String = owned.ref() // &String (reference)
val doubleBorrowed: Ref<String> = borrowed.ref() // &&String
Create mutable references with two available syntaxes:
// Preferred: MutRef() function - generates &mut (&value).clone()
val number: I64 = 42
val mutableRef: MutRef<I64> = MutRef(number)
// Alternative: .mutRef() method - generates &mut value
// Chain directly without binding to avoid immutability issues
val mutableRef2: MutRef<I64> = number.ref().clone().mutRef()
Example with function:
fun modify(value: MutRef<I64>) {
// Function accepting a mutable reference
}
fun main() {
val number: I64 = 42
val mutableRef: MutRef<I64> = MutRef(number)
modify(mutableRef)
}
Transpiles to:
fn modify(value: &mut i64) {
// Function accepting a mutable reference
}
fn main() {
let number: i64 = 42;
let mutable_ref: &mut i64 = &mut (&number).clone();
modify(mutable_ref);
}
The MutRef()
function automatically handles the borrow-and-clone pattern, making it the preferred approach for creating mutable references in Veltrano's immutability-first design.
Veltrano uses val
for immutable variable bindings:
val name: Str = "Alice" // String slice (explicitly typed)
val age = 25 // Type inference (I64)
val message = "Hello, World!" // Type inference (Str)
val owned: Own<String> = "Bob".toString() // Owned string
Functions are declared with fun
:
// Basic function
fun greet(name: String) {
println("Hello, {}!", name)
}
// With return type
fun add(a: I64, b: I64): I64 {
return a + b
}
// Expression body (explicit return needed)
fun multiply(x: I64, y: I64): I64 {
return x * y
}
if (x > 0) {
println("positive")
} else if (x < 0) {
println("negative")
} else {
println("zero")
}
val counter = 0
while (counter < 10) {
println("{}", counter)
// counter would need to be mutable to increment
}
// Infinite loops are converted to Rust's loop
while (true) {
// Becomes: loop { ... }
}
Both line and block comments are supported:
// This is a line comment
/* This is a
block comment */
val x = 42 // Inline comment
/*
* Documentation-style
* block comment
*/
fun documented() {
// Implementation
}
The Nothing
type represents computations that never return:
fun abort(message: Str): Nothing {
panic("{}", message) // Never returns
}
fun infiniteLoop(): Nothing {
while (true) {
// Never returns - transpiles to Rust's loop
}
}
Veltrano automatically converts identifiers using these rules:
- Uppercase letters → underscore + lowercase
- Underscores → double underscores
Veltrano (Input) | Rust (Output) |
---|---|
calculateSum |
calculate_sum |
firstName |
first_name |
HTTPSConnection |
_h_t_t_p_s_connection |
getValue |
get_value |
CamelCase |
_camel_case |
XMLParser |
_x_m_l_parser |
var_name |
var__name |
mixed_Case |
mixed___case |
This applies to all identifiers: functions, variables, and parameters.
Example:
fun calculateSum(firstNumber: I64, secondNumber: I64): I64 {
val totalResult: I64 = firstNumber + secondNumber
return totalResult
}
Transpiles to:
fn calculate_sum(first_number: i64, second_number: i64) -> i64 {
let total_result: i64 = first_number + second_number;
return total_result;
}
cargo run [OPTIONS] <input-file>
Option | Description |
---|---|
--preserve-comments |
Include comments from source in generated Rust code |
# Basic transpilation
cargo run examples/hello.vl
# Output: examples/hello.rs
# With preserved comments
cargo run -- --preserve-comments examples/fibonacci.vl
# Output: examples/fibonacci.rs (with comments)
# From any directory
cargo run path/to/myfile.vl
# Output: path/to/myfile.rs
The examples/
directory contains various Veltrano programs demonstrating different features:
hello.vl
- Basic hello world programfibonacci.vl
- Fibonacci sequence calculationstring_types.vl
- Different string type usageref_type.vl
- Reference type demonstrationsmut_ref.vl
- Mutable reference usagetype_symmetry.vl
- Type system symmetry examplesunified_types.vl
- Unified type system examplesnever_type.vl
- Never type (Nothing
) usagecamel_case.vl
- Naming convention examplescomments.vl
- Comment preservation examplesclone_ufcs.vl
- UFCS clone behaviormutable_bindings.vl
- Practical MutRef patternsmutref_syntax_comparison.vl
- Comparison of MutRef syntaxes
Veltrano currently supports only val
(immutable variables) without a var
keyword. This is a deliberate choice based on a semantic mismatch between Kotlin's var
and Rust's let mut
.
In Kotlin, var
is a storage declaration - it only affects whether the variable binding can be reassigned:
// Kotlin
var x = 5
x = 10 // OK - rebinding allowed
val list = mutableListOf(1, 2, 3)
list.add(4) // OK - val doesn't prevent mutation of the data!
In Rust, let mut
has recursive semantics - it affects both the binding AND enables mutation of the data:
// Rust
let mut x = vec![1, 2, 3];
x.push(4); // Mutating the data (requires mut)
x = vec![5, 6]; // Rebinding (also requires mut)
let y = vec![1, 2, 3];
y.push(4); // ERROR - cannot mutate without mut
This fundamental difference makes a direct mapping potentially confusing. For now, Veltrano takes an immutability-first approach, using MutRef<T>
for cases where mutation is needed.
Most types in Veltrano are references by default because:
- Borrowing is more common than ownership in typical Rust code
- Explicit ownership with
Own<T>
makes ownership transfers visible - Prevents accidental moves which are a common source of Rust learner confusion
- Aligns with Rust's philosophy of preferring borrowing
This design choice means:
String
in Veltrano is&String
in Rust (notString
)- To get an owned string, use
Own<String>
- Basic types like
I64
andBool
remain owned (matching Rust's Copy types)
Contributions are welcome! Please feel free to submit issues and pull requests.
# Clone and build
git clone https://github.com/Portfoligno/veltrano-transpiler.git
cd veltrano-transpiler
cargo build
# Run tests
cargo test
# Run with example
cargo run examples/hello.vl
Developed with Claude Code. If you're interested in trying it:
- Direct link: https://claude.ai/code
- My referral link (I may receive rewards): https://claude.ai/referral/hiwjK__vDg
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.