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), 17 HIR items)The HIR item count includes the items of the implicit prelude, so it is larger than the number of declarations in your file.
Step 4: Compile the Program
Generate a native executable:
cargo run -p neurc -- compile hello.nrExpected output:
Successfully compiled hello.nr -> helloOn 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
Each part of hello.nr:
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
Two bindings and an addition:
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 -- run hello.nr
echo $? # Unix: 30 (PowerShell: echo $LASTEXITCODE)Using 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
A second function, called from main:
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 -- run hello.nr
echo $? # 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 so far:
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:
examples/basics/factorial.nr is the same function
with explicit returns, printing each multiplication as the recursion unwinds:
cargo run -p neurc -- run examples/basics/factorial.nr
echo $? # 120Best Practices
1. Prefer Immutable Variables
// Good
val x: i32 = 10
// Only use mut when necessary
mut counter: i32 = 0
counter = 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 clear
val count: i32 = 423. Use Implicit Returns for Simple Functions
// Clean and concise
func add(a: i32, b: i32) -> i32 {
a + b
}
// Use explicit return for complex logic
func complex(x: i32) -> i32 {
if x > 0 {
return x * 2
}
return 0
}4. Keep Functions Small
// Good: focused functions
func 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 the Return Type
A function with no -> returns nothing, so returning a value from it is an error:
// Error: return type mismatch: expected void, found i32
func bad() {
return 0
}
// Correct
func good() -> i32 {
return 0
}2. Type Mismatch
// Error: type mismatch
func wrong() -> i32 {
return true // bool, not i32
}
// Correct
func right() -> i32 {
return 42
}3. Assigning to Immutable Variable
// Error: cannot assign to immutable variable
func bad() -> i32 {
val x: i32 = 0
x = 10 // Error: x is immutable
return x
}
// Correct
func 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 semicolon
func 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:
- Types: the type system
- Functions: generics, closures, traits
- Control Flow: loops,
match,val-else - CLI Usage: every command and flag
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 exit code for power(3, 4): 81. An exit status holds 0 to 255, so a larger result
such as power(2, 8) shows up as 256 % 256, which is 0.
func power(base: i32, exp: i32) -> i32 {
if exp == 0 {
1
} else {
base * power(base, exp - 1)
}
}
func main() -> i32 {
power(3, 4)
}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:
- Read the error message: it gives the file, line and column of the problem
- Review the Troubleshooting Guide
- Consult the Language Reference
- Report bugs: https://github.com/PanzerPeter/Neuro/issues