I'll take a look at your Tungsten project to better understand your current approach and provide informed recommendations.Let me examine the implementation details to better understand your current approach:Based on your Tungsten project and the goal of creating a Wolfram-like language that transpiles to Rust with its safety guarantees, here are my recommendations:
Your current approach has a solid foundation - creating a statically-typed, functional language with Wolfram syntax that compiles to efficient native code is a great concept. However, there are several considerations for maximizing Rust's safety guarantees:
Instead of compiling directly to native code, I'd recommend transpiling to idiomatic Rust code first. This approach offers several advantages:
- Automatic Safety Guarantees: You inherit all of Rust's memory safety, thread safety, and ownership system
- Ecosystem Access: Generated Rust code can use any Rust crate
- Optimization: Leverage LLVM through rustc's mature optimization pipeline
- Debugging: Users can inspect the generated Rust code
- Gradual Refinement: You can iterate on generating more idiomatic Rust over time
Design your type system to map cleanly to Rust's:
- Wolfram's
List→ Rust'sVec<T>or&[T] - Wolfram's associations →
HashMap<K, V> - Pure functions → Rust functions with no side effects
- Pattern matching → Rust's pattern matching
This is crucial for safety. Consider these approaches:
Option A: Automatic Lifetime Inference
- Analyze data flow in your functional code
- Generate appropriate borrowing and ownership transfers
- Use
Rc<T>orArc<T>for shared data when needed
Option B: Explicit Ownership Annotations
(* Hypothetical syntax *)
f[x: &int] := Add[x, 1] (* borrows *)
g[x: int] := x (* takes ownership *)Here's a phased approach I'd recommend:
Phase 1: Core Transpiler
- Parse Wolfram-like syntax into AST
- Transform AST to Rust AST (using
synandquotecrates) - Generate basic Rust code for arithmetic and functions
Phase 2: Type System
- Implement type inference
- Map complex types (lists, maps, options)
- Handle generic functions
Phase 3: Safety Features
- Ownership analysis
- Automatic lifetime insertion
- Error handling (Result types)
Your Tungsten code:
f[x, y] := Power[x, y]
result = Map[f[2, _], [1, 2, 3]]Could transpile to:
fn f(x: i32, y: i32) -> i32 {
x.pow(y as u32)
}
let result: Vec<i32> = vec![1, 2, 3]
.iter()
.map(|y| f(2, *y))
.collect();- Use a Proper Parser Generator: Consider
lalrpoporpestfor parsing - Leverage
synandquote: These crates make Rust code generation much easier - Incremental Compilation: Support module-by-module transpilation
- Runtime Library: Create a minimal runtime in Rust for Wolfram built-ins
- Error Mapping: Transform Wolfram-style errors to Rust's Result/Option types
If you want to explore other directions:
- LLVM IR Generation: More control but loses Rust's high-level safety abstractions
- MIR Backend: Compile to Rust's Mid-level IR (experimental, requires nightly)
- WebAssembly Target: Compile to WASM with Rust-like safety model
I recommend continuing with your Tungsten project but pivoting to transpilation to Rust rather than direct compilation. This gives you:
- All of Rust's safety guarantees "for free"
- A simpler implementation path
- Better debugging and ecosystem integration
- The ability to gradually improve the generated code quality
The main challenge will be mapping Wolfram's dynamic patterns to Rust's static type system, but with careful design of your type inference and ownership analysis, this is definitely achievable.
Since you're targeting Rust's stdlib functionality, you can create a much cleaner 1:1 mapping:
(* Tungsten/W syntax *)
List::Map[Square, [1, 2, 3, 4]]
String::Split["hello world", " "]
Option::Some[42]
Result::Ok["success"]Transpiles to:
vec![1, 2, 3, 4].iter().map(|x| x * x).collect()
"hello world".split(" ").collect()
Some(42)
Ok("success")Mirror Rust's module structure directly:
(* W's std library modules *)
Collections::HashMap::New[]
Collections::Vec::WithCapacity[10]
Sync::Arc::New[data]
Thread::Spawn[function]
IO::File::Open["path.txt"]Ownership in Functional Style:
(* Explicit move/borrow semantics *)
process[data] := Transform[data] (* moves by default *)
peek[&data] := Print[data] (* explicit borrow *)
share[Arc[data]] := Thread::Spawn[...] (* explicit Arc *)Pattern Matching:
Match[value,
Some[x] -> Multiply[x, 2],
None -> 0
]Error Handling:
Try[
File::Read["config.json"],
Ok[content] -> Parse[content],
Err[e] -> Default[]
]Since you're not implementing Wolfram's symbolic computation, infinite precision arithmetic, or massive built-in library, your transpiler becomes much simpler:
Phase 1: Core Transpilation
- Function definitions → Rust functions
- Basic types (int, float, string, bool) → Rust primitives
- Lists → Vec
- Maps → HashMap<K, V>
Phase 2: Rust-Specific Features
- Option/Result types
- Iterators and closures
- Traits as "interfaces"
- Modules and visibility
Phase 3: Advanced Features
- Async/await syntax
- Unsafe blocks (with clear marking)
- Macros (possibly)
Here's how a more complex W program might look:
Module[IO, Collections];
processFile[path: String] -> Result[Vec[int], String] :=
Let[
content <- File::ReadToString[path],
lines = String::Lines[content],
numbers = List::Map[
line -> Parse::Int[line],
lines
]
] In Result::Ok[numbers]
main[] :=
Match[processFile["numbers.txt"],
Ok[nums] -> Print[List::Sum[nums]],
Err[msg] -> Print::Stderr[msg]
]Transpiles to:
use std::io;
use std::collections;
fn process_file(path: String) -> Result<Vec<i32>, String> {
let content = std::fs::read_to_string(path)
.map_err(|e| e.to_string())?;
let lines = content.lines();
let numbers: Vec<i32> = lines
.map(|line| line.parse().unwrap())
.collect();
Ok(numbers)
}
fn main() {
match process_file("numbers.txt".to_string()) {
Ok(nums) => println!("{}", nums.iter().sum::<i32>()),
Err(msg) => eprintln!("{}", msg),
}
}- No impedance mismatch: You're not trying to force Wolfram's everything-is-an-expression model onto Rust's statement-based model
- Clear mental model: Users know they're getting Rust semantics with Wolfram-like syntax
- Predictable performance: No hidden allocations or symbolic computation overhead
- Full safety: Direct mapping means all of Rust's safety guarantees apply
- Learning curve: Helps users understand Rust concepts through familiar functional syntax
Keep Wolfram's bracket notation but adapt it:
(* Function application *)
Add[1, 2, 3] (* variadic for common operations *)
(* Method calls *)
list::Map[f] (* :: for method syntax *)
(* Type annotations *)
x: int = 5
f[x: &str] -> Option[int] := ...
(* Modules *)
Use[std::collections::HashMap as Map];- Symbolic computation
- Arbitrary precision arithmetic (use Rust's types)
- Dynamic typing
- Notebook interface (at least initially)
- Built-in plotting/visualization
- Massive standard library
- Explicit ownership markers
- Explicit error handling
- Module system matching Rust's
- Trait definitions
- Async/await support
This approach makes Tungsten/W a syntax skin over Rust rather than a separate language trying to achieve Rust's guarantees. It's like how ReScript is to JavaScript, or how Zig relates to C - familiar syntax with modern semantics.
Would you like me to detail any specific aspect, like how to handle traits, lifetimes, or specific stdlib modules in the W syntax?