Beginner

Variables and Types in Eiffel

Learn about variables, data types, type conversions, and Design by Contract type safety in Eiffel with practical Docker-ready examples

In Eiffel, variables are called attributes when they belong to a class and locals when they exist within a routine. As a statically and strongly typed object-oriented language, every variable must have a declared type, and the compiler enforces type correctness at compile time. This catches entire categories of bugs before your code ever runs.

What makes Eiffel’s approach to variables distinctive is how it integrates with Design by Contract. You don’t just declare a variable’s type — you can also specify invariants about its valid values, and the runtime will verify those contracts. Combined with Eiffel’s void safety mechanism, which prevents null reference errors by distinguishing between attached and detachable types, the type system becomes a powerful tool for building reliable software.

In this tutorial, you’ll learn how to declare and use attributes and local variables, work with Eiffel’s built-in types, perform type conversions, and use constants. You’ll also see how Eiffel’s type system interacts with its contract mechanism.

Basic Types and Variable Declarations

Eiffel provides a set of fundamental types that form the building blocks for all programs. Attributes are declared in feature clauses, while local variables are declared in local sections within routines.

Create a file named variables.e:

class
    VARIABLES

create
    make

feature -- Initialization

    make
            -- Demonstrate basic variable types and declarations.
        local
            an_integer: INTEGER
            a_real: REAL_64
            a_character: CHARACTER
            a_boolean: BOOLEAN
            a_natural: NATURAL_32
            an_integer_8: INTEGER_8
            an_integer_64: INTEGER_64
        do
            -- Integer types
            an_integer := 42
            an_integer_8 := 127
            an_integer_64 := 9_000_000_000
            a_natural := 255

            print ("=== Integer Types ===%N")
            print ("INTEGER: " + an_integer.out + "%N")
            print ("INTEGER_8: " + an_integer_8.out + "%N")
            print ("INTEGER_64: " + an_integer_64.out + "%N")
            print ("NATURAL_32: " + a_natural.out + "%N")

            -- Floating-point types
            a_real := 3.14159

            print ("%N=== Floating-Point Types ===%N")
            print ("REAL_64: " + a_real.out + "%N")

            -- Character type
            a_character := 'A'

            print ("%N=== Character Type ===%N")
            print ("CHARACTER: " + a_character.out + "%N")

            -- Boolean type
            a_boolean := True

            print ("%N=== Boolean Type ===%N")
            print ("BOOLEAN: " + a_boolean.out + "%N")

            -- String type (reference type)
            demonstrate_strings
            demonstrate_type_conversions
            demonstrate_constants
        end

feature -- String demonstrations

    demonstrate_strings
            -- Show string variables and operations.
        local
            greeting: STRING
            name: STRING
            combined: STRING
        do
            greeting := "Hello"
            name := "Eiffel"
            combined := greeting + ", " + name + "!"

            print ("%N=== String Type ===%N")
            print ("greeting: " + greeting + "%N")
            print ("name: " + name + "%N")
            print ("combined: " + combined + "%N")
            print ("Length of combined: " + combined.count.out + "%N")
        end

feature -- Type conversions

    demonstrate_type_conversions
            -- Show how to convert between types.
        local
            i: INTEGER
            r: REAL_64
            s: STRING
            b: BOOLEAN
        do
            print ("%N=== Type Conversions ===%N")

            -- Integer to Real
            i := 7
            r := i.to_double
            print ("Integer " + i.out + " to Real: " + r.out + "%N")

            -- Real to Integer (truncates)
            r := 9.81
            i := r.truncated_to_integer
            print ("Real " + r.out + " truncated to Integer: " + i.out + "%N")

            -- Integer to String
            i := 42
            s := i.out
            print ("Integer " + i.out + " to String: %"" + s + "%"%N")

            -- Boolean to String
            b := True
            print ("Boolean to String: " + b.out + "%N")
        end

feature -- Constants

    Pi: REAL_64 = 3.14159265358979
            -- The mathematical constant pi.

    Max_attempts: INTEGER = 10
            -- Maximum number of retry attempts.

    App_name: STRING = "Eiffel Variables Demo"
            -- Application name.

    demonstrate_constants
            -- Show constant usage.
        do
            print ("%N=== Constants ===%N")
            print ("Pi: " + Pi.out + "%N")
            print ("Max attempts: " + Max_attempts.out + "%N")
            print ("App name: " + App_name + "%N")
        end

end

This example covers the core built-in types: INTEGER (with its sized variants), REAL_64 for floating-point numbers, CHARACTER, BOOLEAN, and STRING. Notice how Eiffel uses underscore-separated numeric literals like 9_000_000_000 for readability, and how every type has an out feature that converts it to a string representation.

Void Safety and Detachable Types

One of Eiffel’s most important type system features is void safety — the compiler’s guarantee that you won’t encounter null reference errors at runtime. By default, all reference types are “attached,” meaning they can never be void (null). If a variable might legitimately have no value, you must declare it as detachable and check before using it.

Create a file named variables_void_safety.e:

class
    VARIABLES_VOID_SAFETY

create
    make

feature -- Initialization

    make
            -- Demonstrate void safety and detachable types.
        do
            print ("=== Void Safety ===%N")
            demonstrate_attached
            demonstrate_detachable
        end

feature -- Attached vs Detachable

    name: STRING
            -- An attached attribute (cannot be Void after creation).
        attribute
            Result := "Default"
        end

    nickname: detachable STRING
            -- A detachable attribute (can be Void).

    demonstrate_attached
            -- Show attached (non-void) attribute usage.
        do
            name := "Eiffel Developer"
            print ("Name: " + name + "%N")
            print ("Name length: " + name.count.out + "%N")
        end

    demonstrate_detachable
            -- Show detachable (possibly void) attribute usage.
        do
            -- nickname is Void by default
            print ("%N=== Detachable Types ===%N")

            if attached nickname as n then
                print ("Nickname: " + n + "%N")
            else
                print ("Nickname is Void (not set)%N")
            end

            -- Now assign a value
            nickname := "Code Archaeologist"

            if attached nickname as n then
                print ("Nickname after assignment: " + n + "%N")
                print ("Nickname length: " + n.count.out + "%N")
            else
                print ("Nickname is Void%N")
            end

            -- Demonstrate object test with detachable
            check_value (nickname)
            check_value (Void)
        end

    check_value (val: detachable STRING)
            -- Check whether a detachable value is attached.
        do
            if attached val as v then
                print ("Value is attached: " + v + "%N")
            else
                print ("Value is Void%N")
            end
        end

end

The attached keyword serves as both a type annotation and a runtime check. The pattern if attached nickname as n then is an “object test” — it checks whether the detachable reference holds a value, and if so, binds it to the local name n which is guaranteed to be non-void within that block. This eliminates the possibility of null pointer exceptions.

Attributes with Contracts

Eiffel’s type system works hand-in-hand with Design by Contract. You can declare class invariants that constrain attribute values, and the runtime will verify them after every public routine call.

Create a file named variables_contracts.e:

class
    VARIABLES_CONTRACTS

create
    make

feature -- Initialization

    make
            -- Demonstrate variables with contracts.
        do
            print ("=== Variables with Contracts ===%N")

            set_temperature (72.0)
            print ("Temperature: " + temperature.out + " F%N")

            set_percentage (85)
            print ("Percentage: " + percentage.out + "%%%N")

            set_name ("Eiffel")
            print ("Name: " + name + "%N")

            print ("%N=== Expanded vs Reference Types ===%N")
            print ("INTEGER is expanded (value type): " + percentage.out + "%N")
            print ("STRING is reference type: " + name + "%N")

            print ("%N=== Default Values ===%N")
            print ("Default INTEGER: " + default_int.out + "%N")
            print ("Default REAL_64: " + default_real.out + "%N")
            print ("Default BOOLEAN: " + default_bool.out + "%N")
            print ("Default CHARACTER: [" + default_char.out + "]%N")
        end

feature -- Access

    temperature: REAL_64
            -- Current temperature in Fahrenheit.

    percentage: INTEGER
            -- A percentage value between 0 and 100.

    name: STRING
            -- A non-empty name.
        attribute
            Result := "Unknown"
        end

feature -- Default value demonstrations

    default_int: INTEGER
            -- Shows default INTEGER value (0).

    default_real: REAL_64
            -- Shows default REAL_64 value (0.0).

    default_bool: BOOLEAN
            -- Shows default BOOLEAN value (False).

    default_char: CHARACTER
            -- Shows default CHARACTER value (null character).

feature -- Element change

    set_temperature (a_temp: REAL_64)
            -- Set temperature with contract.
        require
            reasonable_temp: a_temp >= -459.67 and a_temp <= 1000.0
        do
            temperature := a_temp
        ensure
            temperature_set: temperature = a_temp
        end

    set_percentage (a_value: INTEGER)
            -- Set percentage with bounds checking.
        require
            valid_percentage: a_value >= 0 and a_value <= 100
        do
            percentage := a_value
        ensure
            percentage_set: percentage = a_value
        end

    set_name (a_name: STRING)
            -- Set name with non-empty constraint.
        require
            name_not_empty: not a_name.is_empty
        do
            name := a_name.twin
        ensure
            name_set: name.is_equal (a_name)
        end

invariant
    valid_percentage: percentage >= 0 and percentage <= 100
    valid_name: not name.is_empty

end

This example shows how contracts extend the type system. The percentage attribute is an INTEGER, but the class invariant guarantees it’s always between 0 and 100. The set_percentage routine’s precondition checks the input, the postcondition verifies the assignment, and the invariant ensures the constraint holds at all times. Notice also the distinction between expanded types (value types like INTEGER, REAL_64, BOOLEAN, CHARACTER) which have default values, and reference types (like STRING) which default to Void unless given an attribute body.

Running with Docker

Eiffel requires an ECF configuration file for compilation. Each example needs its own ECF file with the correct root class name.

1
2
3
4
5
# Pull the official image
docker pull eiffel/eiffel:latest

# Run the basic variables example
docker run --rm -v $(pwd):/app -w /app eiffel/eiffel:latest sh -c 'echo PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iSVNPLTg4NTktMSI/Pgo8c3lzdGVtIHhtbG5zPSJodHRwOi8vd3d3LmVpZmZlbC5jb20vZGV2ZWxvcGVycy94bWwvY29uZmlndXJhdGlvbi0xLTIyLTAiIG5hbWU9InZhcmlhYmxlcyIgdXVpZD0iMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAyIj4KPHRhcmdldCBuYW1lPSJ2YXJpYWJsZXMiPjxyb290IGNsYXNzPSJWQVJJQUJMRVMiIGZlYXR1cmU9Im1ha2UiLz48c2V0dGluZyBuYW1lPSJjb25zb2xlX2FwcGxpY2F0aW9uIiB2YWx1ZT0idHJ1ZSIvPgo8bGlicmFyeSBuYW1lPSJiYXNlIiBsb2NhdGlvbj0iJElTRV9MSUJSQVJZL2xpYnJhcnkvYmFzZS9iYXNlLmVjZiIvPjxjbHVzdGVyIG5hbWU9InJvb3RfY2x1c3RlciIgbG9jYXRpb249Ii4iLz48L3RhcmdldD48L3N5c3RlbT4K | base64 -d > variables.ecf && ec -batch -config variables.ecf -c_compile && ./EIFGENs/variables/W_code/variables'

To run the void safety or contracts examples, change the root class in the ECF. For example, for variables_void_safety.e:

1
docker run --rm -v $(pwd):/app -w /app eiffel/eiffel:latest sh -c 'echo PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iSVNPLTg4NTktMSI/Pgo8c3lzdGVtIHhtbG5zPSJodHRwOi8vd3d3LmVpZmZlbC5jb20vZGV2ZWxvcGVycy94bWwvY29uZmlndXJhdGlvbi0xLTIyLTAiIG5hbWU9InZvaWRfc2FmZXR5IiB1dWlkPSIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDMiPgo8dGFyZ2V0IG5hbWU9InZvaWRfc2FmZXR5Ij48cm9vdCBjbGFzcz0iVkFSSUFCTEVTX1ZPSURfU0FGRVRZIiBmZWF0dXJlPSJtYWtlIi8+PHNldHRpbmcgbmFtZT0iY29uc29sZV9hcHBsaWNhdGlvbiIgdmFsdWU9InRydWUiLz4KPGxpYnJhcnkgbmFtZT0iYmFzZSIgbG9jYXRpb249IiRJU0VfTElCUkFSWS9saWJyYXJ5L2Jhc2UvYmFzZS5lY2YiLz48Y2x1c3RlciBuYW1lPSJyb290X2NsdXN0ZXIiIGxvY2F0aW9uPSIuIi8+PC90YXJnZXQ+PC9zeXN0ZW0+Cg== | base64 -d > void_safety.ecf && ec -batch -config void_safety.ecf -c_compile && ./EIFGENs/void_safety/W_code/void_safety'

Expected Output

For the basic variables example (variables.e):

=== Integer Types ===
INTEGER: 42
INTEGER_8: 127
INTEGER_64: 9000000000
NATURAL_32: 255

=== Floating-Point Types ===
REAL_64: 3.14159

=== Character Type ===
CHARACTER: A

=== Boolean Type ===
BOOLEAN: True

=== String Type ===
greeting: Hello
name: Eiffel
combined: Hello, Eiffel!
Length of combined: 14

=== Type Conversions ===
Integer 7 to Real: 7
Real 9.81 truncated to Integer: 9
Integer 42 to String: "42"
Boolean to String: True

=== Constants ===
Pi: 3.14159265358979
Max attempts: 10
App name: Eiffel Variables Demo

For the void safety example (variables_void_safety.e):

=== Void Safety ===
Name: Eiffel Developer
Name length: 16

=== Detachable Types ===
Nickname is Void (not set)
Nickname after assignment: Code Archaeologist
Nickname length: 18
Value is attached: Code Archaeologist
Value is Void

For the contracts example (variables_contracts.e):

=== Variables with Contracts ===
Temperature: 72 F
Percentage: 85%
Name: Eiffel

=== Expanded vs Reference Types ===
INTEGER is expanded (value type): 85
STRING is reference type: Eiffel

=== Default Values ===
Default INTEGER: 0
Default REAL_64: 0
Default BOOLEAN: False
Default CHARACTER: [ ]

Key Concepts

  • All variables are typed — Eiffel is statically and strongly typed; every attribute and local must have a declared type, and the compiler enforces type correctness
  • Expanded vs reference types — Expanded types (INTEGER, REAL_64, BOOLEAN, CHARACTER) are value types with defaults (0, 0.0, False, null character); reference types (STRING, custom classes) default to Void
  • Void safety — By default, reference types are “attached” and cannot be Void; use detachable to allow null values, and attached ... as object tests to safely access them
  • Constants are class features — Constants are declared as attributes with an = value assignment directly in the feature declaration, not as local variables
  • Type conversions use feature calls — Convert with methods like to_double, truncated_to_integer, and out (to string) rather than casting syntax
  • Contracts extend the type system — Preconditions, postconditions, and class invariants let you constrain variable values beyond what the type alone guarantees
  • One class per file — Each .e file contains exactly one class, with the filename matching the class name in lowercase (e.g., variables.e for class VARIABLES)
  • String special characters use % — Eiffel uses %N for newline, %T for tab, and %% for a literal percent sign, not backslash escapes

Running Today

All examples can be run using Docker:

docker pull eiffel/eiffel:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining