Julia’s type system is one of its greatest strengths. It’s dynamically typed—you don’t need to declare types—but under the hood, Julia’s compiler uses type inference to generate code as fast as C or Fortran. This means you get the flexibility of Python with the performance of a statically typed language.
What makes Julia’s approach distinctive is how types interact with multiple dispatch. Every value has a concrete type, and those types form a hierarchy that the dispatch system uses to select the right method. Understanding this type system is essential to writing idiomatic Julia.
In this tutorial, you’ll learn how Julia handles variables, explore its built-in types, work with type conversions, and see how the type hierarchy enables Julia’s powerful dispatch system.
Variable Basics and Numeric Types
Julia variables are created by assignment—no declaration keyword needed. Julia infers the type automatically and compiles specialized code for each type it encounters.
Create a file named variables.jl:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
| # Variable assignment - no keywords needed
x = 42
pi_approx = 3.14159
name = "Julia"
is_fast = true
# Check types with typeof()
println("x: $x ($(typeof(x)))")
println("pi_approx: $pi_approx ($(typeof(pi_approx)))")
println("name: $name ($(typeof(name)))")
println("is_fast: $is_fast ($(typeof(is_fast)))")
println()
# Integer types - Julia chooses Int64 on 64-bit systems
a = 10 # Int64
b = 0xff # UInt8 (hexadecimal literal)
c = 0b1010 # UInt8 (binary literal)
big_num = 10_000_000 # Underscores for readability
println("a: $a ($(typeof(a)))")
println("b: $b ($(typeof(b)))")
println("c: $c ($(typeof(c)))")
println("big_num: $big_num ($(typeof(big_num)))")
println()
# Floating-point types
f1 = 1.0 # Float64 (default)
f2 = 1.0f0 # Float32 (note the 'f' suffix)
f3 = 2.5e3 # Scientific notation: 2500.0
println("f1: $f1 ($(typeof(f1)))")
println("f2: $f2 ($(typeof(f2)))")
println("f3: $f3 ($(typeof(f3)))")
println()
# Special numeric values
println("Infinity: $(Inf)")
println("Negative infinity: $(-Inf)")
println("Not a number: $(NaN)")
println("Machine epsilon: $(eps(Float64))")
println()
# Characters vs Strings
ch = 'A' # Char (single quotes)
str = "A" # String (double quotes)
println("'A' is $(typeof(ch)), \"A\" is $(typeof(str))")
println()
# Nothing - Julia's null equivalent
result = nothing
println("nothing: $result ($(typeof(result)))")
|
The Type Hierarchy
Julia’s types form a tree. Abstract types sit at the branch points and concrete types are at the leaves. You can’t instantiate an abstract type, but you can use it for dispatch and to express relationships between types.
Create a file named variables_types.jl:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
| # The type hierarchy - every type has a parent
println("=== Type Hierarchy ===")
println("Int64 <: Signed <: Integer <: Real <: Number")
println(" Int64 supertype: $(supertype(Int64))")
println(" Signed supertype: $(supertype(Signed))")
println(" Integer supertype: $(supertype(Integer))")
println(" Real supertype: $(supertype(Real))")
println()
# Subtype checks with <:
println("=== Subtype Checks ===")
println("Int64 <: Integer => $(Int64 <: Integer)")
println("Float64 <: Integer => $(Float64 <: Integer)")
println("Int64 <: Number => $(Int64 <: Number)")
println("String <: Number => $(String <: Number)")
println()
# Multiple dispatch uses the type hierarchy
describe(x::Integer) = "$x is an integer"
describe(x::AbstractFloat) = "$x is a float"
describe(x::AbstractString) = "\"$x\" is a string"
describe(x::Bool) = "$x is a boolean"
# Bool is checked first because Bool <: Integer
# Julia dispatches to the most specific method
println("=== Multiple Dispatch on Types ===")
println(describe(42))
println(describe(3.14))
println(describe("hello"))
println(describe(true))
println()
# Constants with const
const MAX_ITERATIONS = 1000
const GRAVITY = 9.81
println("=== Constants ===")
println("MAX_ITERATIONS: $MAX_ITERATIONS")
println("GRAVITY: $GRAVITY")
println()
# Type conversions
println("=== Type Conversions ===")
# convert() - safe conversion
int_val = convert(Int64, 3.0)
println("convert(Int64, 3.0) = $int_val ($(typeof(int_val)))")
# Constructor-style conversion
float_val = Float64(42)
println("Float64(42) = $float_val ($(typeof(float_val)))")
# parse() - string to number
parsed = parse(Int64, "123")
println("parse(Int64, \"123\") = $parsed ($(typeof(parsed)))")
# string() - anything to string
str_val = string(42, " is the answer")
println("string(42, \" is the answer\") = \"$str_val\"")
# Round, floor, ceil
println("round(Int64, 3.7) = $(round(Int64, 3.7))")
println("floor(Int64, 3.7) = $(floor(Int64, 3.7))")
println("ceil(Int64, 3.2) = $(ceil(Int64, 3.2))")
println()
# Tuples and NamedTuples - immutable collections
println("=== Tuples ===")
point = (3.0, 4.0)
println("point: $point ($(typeof(point)))")
println("x = $(point[1]), y = $(point[2])")
named = (x=3.0, y=4.0, label="origin")
println("named: $named")
println("named.label = $(named.label)")
println()
# Arrays - Julia's workhorse collection
println("=== Arrays ===")
nums = [1, 2, 3, 4, 5]
println("nums: $nums ($(typeof(nums)))")
mixed = Any[1, "two", 3.0, true]
println("mixed: $mixed ($(typeof(mixed)))")
# Ranges
r = 1:5
println("range 1:5: $r ($(typeof(r)))")
println("collected: $(collect(r))")
# Dictionaries
println()
println("=== Dictionaries ===")
ages = Dict("Alice" => 30, "Bob" => 25)
println("ages: $ages")
println("Alice's age: $(ages["Alice"])")
|
Running with Docker
1
2
3
4
5
6
7
8
| # Pull the official image
docker pull julia:1.11-alpine
# Run the basics example
docker run --rm -v $(pwd):/app -w /app julia:1.11-alpine julia variables.jl
# Run the types example
docker run --rm -v $(pwd):/app -w /app julia:1.11-alpine julia variables_types.jl
|
Expected Output
Output from variables.jl:
x: 42 (Int64)
pi_approx: 3.14159 (Float64)
name: Julia (String)
is_fast: true (Bool)
a: 10 (Int64)
b: 255 (UInt8)
c: 10 (UInt8)
big_num: 10000000 (Int64)
f1: 1.0 (Float64)
f2: 1.0 (Float32)
f3: 2500.0 (Float64)
Infinity: Inf
Negative infinity: -Inf
Not a number: NaN
Machine epsilon: 2.220446049250313e-16
'A' is Char, "A" is String
nothing: nothing (Nothing)
Output from variables_types.jl:
=== Type Hierarchy ===
Int64 <: Signed <: Integer <: Real <: Number
Int64 supertype: Signed
Signed supertype: Integer
Integer supertype: Real
Real supertype: Number
=== Subtype Checks ===
Int64 <: Integer => true
Float64 <: Integer => false
Int64 <: Number => true
String <: Number => false
=== Multiple Dispatch on Types ===
42 is an integer
3.14 is a float
"hello" is a string
true is a boolean
=== Constants ===
MAX_ITERATIONS: 1000
GRAVITY: 9.81
=== Type Conversions ===
convert(Int64, 3.0) = 3 (Int64)
Float64(42) = 42.0 (Float64)
parse(Int64, "123") = 123 (Int64)
string(42, " is the answer") = "42 is the answer"
round(Int64, 3.7) = 4
floor(Int64, 3.7) = 3
ceil(Int64, 3.2) = 4
=== Tuples ===
point: (3.0, 4.0) (Tuple{Float64, Float64})
x = 3.0, y = 4.0
named: (x = 3.0, y = 4.0, label = "origin")
named.label = origin
=== Arrays ===
nums: [1, 2, 3, 4, 5] (Vector{Int64})
mixed: Any[1, "two", 3.0, true] (Vector{Any})
range 1:5: 1:5 (UnitRange{Int64})
collected: [1, 2, 3, 4, 5]
=== Dictionaries ===
ages: Dict("Alice" => 30, "Bob" => 25)
Alice's age: 30
Key Concepts
- Dynamic with type inference: Julia infers types at compile time for performance—you get Python’s convenience with C’s speed.
- Rich numeric type hierarchy: Integer types (
Int8 through Int128, unsigned variants), floating-point types (Float16, Float32, Float64), and arbitrary precision (BigInt, BigFloat). - Types enable multiple dispatch: The type of every argument determines which method gets called—this is Julia’s core paradigm, not just a type-checking feature.
const for constants: Declaring constants with const helps the compiler optimize and signals intent to other developers.- Conversion is explicit: Use
convert(), constructor syntax (Float64(x)), or parse() for strings—Julia won’t silently coerce types in ways that lose information. nothing is Julia’s null: It has its own type Nothing, and functions that don’t return a value return nothing.- Tuples are immutable, arrays are mutable: Tuples and NamedTuples are fixed after creation; arrays (
Vector, Matrix) can grow and change. - 1-based indexing: Arrays and tuples start at index 1, matching mathematical convention.
Comments
Loading comments...
Leave a Comment