← Code Compare

Variables, Binding & Basic Types

The same tiny program in seven languages: bind four immutable values of the four basic scalar types (an Int, a Float, a Str, and a Bool), then use one mutable accumulator to sum 1..count and print a summary. Watch two axes vary: how mutability is opted into (immutable-by-default with mut/let/ref, versus mutable-by-default), and how a value's type is signalled — a leading sigil that is part of the name ($, @, % in guji, Perl, and Raku) versus a bare name whose type lives only in a declaration or annotation (Go, OCaml, Rust, Python).

Show: gujiGoOCamlPerlRakuRustPython
guji
sub main(): Int {
    # Immutable bindings; the sigil ($) is part of the name,
    # types (Int, Float, Str, Bool) are inferred.
    $count = 42
    $pi = 3.14
    $name = "ada"
    $active = true

    # Mutation is opt-in with `mut`; everything else can't be reassigned.
    mut $total = 0
    for $n in 1..$count {
        $total = $total + $n
    }

    print("name=$name active=$active pi=$pi")
    print("sum of 1..$count = $total")
    0
}

Bindings are immutable by default$total needs mut to be reassigned, while $count/$pi/$name/$active cannot change. The $ sigil is part of the name and invariant; the static types Int/Float/Str/Bool are inferred, so no annotation is written. Double-quoted strings interpolate $name directly, and main returns its Int exit code (0).

Go
package main

import "fmt"

func main() {
	// Typed, immutable-ish: const for compile-time constants,
	// var for the rest. Types are explicit or inferred with :=.
	const count int = 42
	const pi float64 = 3.14
	name := "ada"
	active := true

	total := 0 // ordinary variables are mutable
	for n := 1; n <= count; n++ {
		total += n
	}

	fmt.Printf("name=%s active=%t pi=%g\n", name, active, pi)
	fmt.Printf("sum of 1..%d = %d\n", count, total)
}

Go variables are mutable by default; true immutability is reserved for const, which works only for compile-time constants like count and pi. The := short form infers the type from the initializer (name is string, active is bool), while const count int shows the explicit annotation. fmt.Printf verbs (%s, %t, %g, %d) are type-specific, so the types stay visible at the call site.

OCaml
let () =
  (* `let` bindings are immutable values, not variables. *)
  let count = 42 in       (* int   *)
  let pi = 3.14 in        (* float *)
  let name = "ada" in     (* string *)
  let active = true in    (* bool  *)

  (* Mutation needs an explicit mutable cell: a `ref`. *)
  let total = ref 0 in
  for n = 1 to count do
    total := !total + n
  done;

  Printf.printf "name=%s active=%b pi=%g\n" name active pi;
  Printf.printf "sum of 1..%d = %d\n" count !total

In OCaml let introduces an immutable binding, and types are inferred by Hindley–Milner — count : int, pi : float, name : string, active : bool need no annotation. Genuine mutation requires an explicit ref cell, written ref 0, read with !total, and updated with :=. Printf.printf is type-checked against its format string (%d int, %g float, %b bool, %s string).

Perl
use strict;
use warnings;

# Sigils mark shape: $ for a scalar. `my` declares a lexical;
# Perl scalars are typeless and hold any kind of value.
my $count  = 42;
my $pi     = 3.14;
my $name   = "ada";
my $active = 1;            # no native Bool: 1 is true, 0/"" is false

my $total = 0;
$total += $_ for 1 .. $count;

printf "name=%s active=%s pi=%s\n", $name, $active ? "true" : "false", $pi;
print  "sum of 1..$count = $total\n";

Every variable wears a sigil ($ for a scalar) that is part of the name, and my gives it lexical scope. Perl scalars are dynamically typed and mutable by default — the same $total is happily reassigned, and there is no distinct boolean type, so 1/0 (or the empty string) stand in. Strings interpolate $name and $count directly inside double quotes.

Raku
# Sigils are part of the name; optional type constraints add static checks.
my Int $count  = 42;
my Rat $pi     = 3.14;     # decimal literals are exact rationals (Rat)
my Str $name   = "ada";
my Bool $active = True;

# `my` bindings are mutable; immutability is opt-in with the `is readonly`
# trait or by binding with `:=`.
my $total = [+] 1 .. $count;   # reduce 1..count with the + operator

say "name=$name active=$active pi=$pi";
say "sum of 1..$count = $total";

Raku keeps Perl's invariant sigils but adds optional type constraints (my Int $count) that the runtime enforces, and a real Bool type with True/False. A decimal literal like 3.14 is an exact Rat (rational), not a float. The whole sum collapses to [+] 1 .. $count — the [ ] reduction metaoperator folds + over the range — and say stringifies each value.

Rust
fn main() {
    // `let` bindings are immutable by default; types are inferred.
    let count: i64 = 42;
    let pi = 3.14_f64;
    let name = "ada";
    let active = true;

    // Opt into mutation with `mut`.
    let mut total: i64 = 0;
    for n in 1..=count {
        total += n;
    }
    // Idiomatically: let total: i64 = (1..=count).sum();

    println!("name={name} active={active} pi={pi}");
    println!("sum of 1..{count} = {total}");
}

Rust bindings are immutable by default; total needs let mut before it can be reassigned, mirroring guji's mut. Types are inferred but can be annotated (i64, f64), and 1..=count is an inclusive range. The captured-identifier form println!("{count}") interpolates a named binding directly, and the inclusive range also has a .sum() that would replace the explicit loop.

Python
# Names are untyped at runtime; annotations are optional hints.
count: int = 42
pi: float = 3.14
name: str = "ada"
active: bool = True

total = 0  # every binding is mutable; there is no `const`
for n in range(1, count + 1):
    total += n

print(f"name={name} active={active} pi={pi}")
print(f"sum of 1..{count} = {total}")

Python names are mutable by default and dynamically typed — the : int / : float / : str / : bool annotations are optional hints checked by tools, never at runtime. range(1, count + 1) is half-open, so + 1 makes the sum inclusive. f-strings interpolate {name} and {count} the way guji, Perl, and Raku interpolate $name. The whole loop could be sum(range(1, count + 1)).