Your First Neuro Program
This tutorial walks you through writing, compiling, and running your first Neuro program.
Prerequisites
- Neuro compiler installed (Installation Guide)
- Text editor or IDE
- Basic programming knowledge
Step 1: Create a Project Directory
Create a directory for your Neuro programs:
mkdir neuro-projects
cd neuro-projectsStep 2: Write Your First Program
Create a file named hello.nr:
func main() -> i32 { return 0}This is the simplest valid Neuro program:
funcdeclares a functionmainis the entry point (required)-> i32specifies the return type (32-bit signed integer)return 0exits with success code
Step 3: Check the Program
Validate syntax and types:
cargo run -p neurc -- check hello.nrExpected output:
Type checking passed for "hello.nr" (1 module(s), 9 HIR items)
Step 4: Compile the Program
Generate a native executable:
cargo run -p neurc -- compile hello.nrExpected output:
Successfully compiled hello.nr -> hello
On Windows the executable is hello.exe.
Step 5: Run the Program
Execute your program:
# Windows
.\hello.exe
# Unix
./helloThe program runs and returns exit code 0 (success).
Check the exit code:
# Windows (PowerShell)
echo $LASTEXITCODE
# Unix
echo $?Output: 0
Understanding the Program
Let's break down the hello.nr program:
func main() -> i32 { return 0}func: Keyword to declare a functionmain: Function name (required entry point)(): Empty parameter list-> i32: Return type annotation{...}: Function bodyreturn 0: Return statement with value
Adding Variables
Let's make the program more interesting:
func main() -> i32 { val x: i32 = 10 val y: i32 = 20 return x + y}New concepts:
val: Declares an immutable variablex: i32: Variable name and type annotation= 10: Initializer expressionx + y: Arithmetic expression
Compile and run:
cargo run -p neurc -- compile hello.nr
.\hello.exe # Windows
echo $LASTEXITCODE # Output: 30Using Mutable Variables
Neuro supports mutable variables with the mut keyword:
func main() -> i32 { mut counter: i32 = 0 counter = counter + 1 counter = counter + 2 return counter // Returns 3}New concepts:
mut: Declares a mutable variablecounter = ...: Variable reassignment (only for mut variables)
Key points:
- Immutable by default (
val) - Explicit mutability (
mut) - Type safety: reassigned value must match variable type
Adding Functions
Let's create a helper function:
func add(a: i32, b: i32) -> i32 { return a + b} func main() -> i32 { val result: i32 = add(5, 3) return result}New concepts:
- Function parameters:
a: i32, b: i32 - Function calls:
add(5, 3) - Type checking: Arguments must match parameter types
Compile and run:
cargo run -p neurc -- compile hello.nr
.\hello.exe
echo $LASTEXITCODE # Output: 8Expression-Based Returns
Neuro supports implicit returns (Rust-style):
func add(a: i32, b: i32) -> i32 { a + b // Implicit return (no 'return' keyword)} func main() -> i32 { add(5, 3) // Implicit return}Key points:
- The last expression in the body becomes the return value (no
return, no;) - Must match function return type
- Can mix explicit
returnand implicit returns
Control Flow: If/Else
Add conditional logic:
func max(a: i32, b: i32) -> i32 { if a > b { return a } else { return b }} func main() -> i32 { val result: i32 = max(10, 5) return result}New concepts:
ifconditions: Must be boolean expressionselseblocks: Optional alternative pathelse if: Chain multiple conditions
With else-if:
func classify(x: i32) -> i32 { if x > 0 { return 1 // Positive } else if x < 0 { return -1 // Negative } else { return 0 // Zero }} func main() -> i32 { return classify(5) // Returns 1}Working with Different Types
Integers
Neuro supports multiple integer types:
func main() -> i32 { val tiny: i8 = 127 // 8-bit signed val small: i16 = 32767 // 16-bit signed val normal: i32 = 42 // 32-bit signed (default) val big: i64 = 9999999 // 64-bit signed val byte: u8 = 255 // 8-bit unsigned val word: u16 = 65535 // 16-bit unsigned val dword: u32 = 42 // 32-bit unsigned val qword: u64 = 99999 // 64-bit unsigned return normal}Floats
func main() -> i32 { val pi: f32 = 3.14159 // 32-bit float val e: f64 = 2.71828 // 64-bit float (default) val result: f32 = pi * 2.0 // 2.0 infers as f32 from context val wide: f64 = pi as f64 * 2.0 // explicit cast to mix widths return 0}Note: Float literals default to f64. When an annotated type is in context (e.g. val x: f32 = 2.0), the literal infers as f32.
Booleans
func main() -> i32 { val is_ready: bool = true val is_error: bool = false if is_ready && !is_error { return 1 } else { return 0 }}Operators
Arithmetic
func main() -> i32 { val a: i32 = 10 val b: i32 = 3 val sum: i32 = a + b // 13 val diff: i32 = a - b // 7 val prod: i32 = a * b // 30 val quot: i32 = a / b // 3 val rem: i32 = a % b // 1 return sum}Comparison
func main() -> i32 { val x: i32 = 10 val eq: bool = x == 10 // true val ne: bool = x != 5 // true val lt: bool = x < 20 // true val gt: bool = x > 5 // true val le: bool = x <= 10 // true val ge: bool = x >= 10 // true if eq { return 1 } else { return 0 }}Logical
func main() -> i32 { val a: bool = true val b: bool = false val and_result: bool = a && b // false val or_result: bool = a || b // true val not_result: bool = !a // false if a && !b { return 1 // This executes } else { return 0 }}Complete Example: Factorial
Combining everything we've learned:
func factorial(n: i32) -> i32 { if n <= 1 { 1 // Implicit return } else { n * factorial(n - 1) // Implicit return with recursion }} func main() -> i32 { val result: i32 = factorial(5) result // Implicit return: 120}Compile and run:
cargo run -p neurc -- compile examples/basics/factorial.nr
.\factorial.exe
echo $LASTEXITCODE # Output: 120Best Practices
1. Prefer Immutable Variables
// Goodval x: i32 = 10 // Only use mut when necessarymut counter: i32 = 0counter = counter + 12. Use Explicit Types
Inference works (val count = 42 is an i32), but an annotation documents intent
at a binding a reader has to trust:
// Explicit and clearval count: i32 = 423. Use Implicit Returns for Simple Functions
// Clean and concisefunc add(a: i32, b: i32) -> i32 { a + b} // Use explicit return for complex logicfunc complex(x: i32) -> i32 { if x > 0 { return x * 2 } return 0}4. Keep Functions Small
// Good: focused functionsfunc is_positive(x: i32) -> bool { x > 0} func is_negative(x: i32) -> bool { x < 0} func classify(x: i32) -> i32 { if is_positive(x) { return 1 } else if is_negative(x) { return -1 } else { return 0 }}Common Mistakes
1. Forgetting Return Type
// Error: missing return typefunc bad() { return 0} // Correctfunc good() -> i32 { return 0}2. Type Mismatch
// Error: type mismatchfunc wrong() -> i32 { return true // bool, not i32} // Correctfunc right() -> i32 { return 42}3. Assigning to Immutable Variable
// Error: cannot assign to immutable variablefunc bad() -> i32 { val x: i32 = 0 x = 10 // Error: x is immutable return x} // Correctfunc good() -> i32 { mut x: i32 = 0 x = 10 // OK: x is mutable return x}4. Statements vs Implicit Return
Neuro has no semicolons: statements are terminated by a newline. A trailing
; is a parse error. The final expression in a function body (no return
keyword) becomes its return value:
func example() -> i32 { val x: i32 = 10 // Statement (newline-terminated, no `;`) x // Last expression is the implicit return value} // Common mistake: a stray semicolonfunc wrong() -> i32 { val x: i32 = 10 x; // Error: `;` is not a valid token here}What's Next?
Now that you've written your first programs, explore:
- Language Reference - Complete type system documentation
- Functions Guide - Advanced function features
- Control Flow - Conditional logic in detail
- CLI Usage - Advanced compiler usage
Practice Exercises
Try implementing these programs:
Exercise 1: Fibonacci
Write a function that computes the nth Fibonacci number.
Expected output for fibonacci(10): 55
func fibonacci(n: i32) -> i32 { if n <= 1 { n } else { fibonacci(n - 1) + fibonacci(n - 2) }} func main() -> i32 { fibonacci(10)}Exercise 2: Power
Write a function that computes x^n (x to the power of n).
Expected output for power(2, 8): 256
func power(base: i32, exp: i32) -> i32 { if exp == 0 { 1 } else { base * power(base, exp - 1) }} func main() -> i32 { power(2, 8)}Exercise 3: Is Prime
Write a function that checks if a number is prime.
Return 1 for prime, 0 for not prime.
<details> <summary>Solution</summary>func is_prime_helper(n: i32, divisor: i32) -> bool { if divisor * divisor > n { true } else if n % divisor == 0 { false } else { is_prime_helper(n, divisor + 1) }} func is_prime(n: i32) -> bool { if n <= 1 { false } else { is_prime_helper(n, 2) }} func main() -> i32 { if is_prime(17) { 1 } else { 0 }}Getting Help
If you encounter issues:
- Check the error message carefully - it includes the exact location and problem
- Review the Troubleshooting Guide
- Consult the Language Reference
- Report bugs: https://github.com/PanzerPeter/Neuro/issues