ZetCode

F# boxing and unboxing

last modified May 3, 2025

In this tutorial, we will delve into boxing and unboxing in F# and their impact on performance and memory management.

Boxing and unboxing are mechanisms for converting between value types (e.g., integers, floats) and reference types (objects). Boxing encapsulates a value type within an object, allowing it to be treated as a reference type. Conversely, unboxing retrieves the original value type from the object.

While these operations enable flexibility when working with heterogeneous data, they can introduce performance overhead due to additional memory allocation and type conversion. Understanding how and when to use boxing and unboxing efficiently is essential for writing optimized F# code.

The box keyword is used to box a value type, while the unbox keyword is used to unbox a reference type back to its original value type.

F# boxing example

Boxing converts a value type to System.Object.

boxing.fsx
let x = 42
let boxed = box x

printfn $"Value: {x}, Type: {x.GetType()}"
printfn $"Boxed: {boxed}, Type: {boxed.GetType()}"

We box an integer value and examine its type before and after.

let boxed = box x

The box keyword converts the value type to System.Object.

λ dotnet fsi boxing.fsx
Value: 42, Type: System.Int32
Boxed: 42, Type: System.Int32

F# unboxing example

Unboxing converts System.Object back to a value type.

unboxing.fsx
let boxed = box 42
let unboxed : int = unbox boxed

printfn $"Boxed: {boxed}, Type: {boxed.GetType()}"
printfn $"Unboxed: {unboxed}, Type: {unboxed.GetType()}"

We unbox an integer value and verify its type.

let unboxed : int = unbox boxed

The unbox keyword extracts the value with type annotation.

λ dotnet fsi unboxing.fsx
Boxed: 42, Type: System.Int32
Unboxed: 42, Type: System.Int32

F# invalid unboxing

Unboxing to the wrong type causes runtime errors.

invalid.fsx
let boxed = box 42

try
    let unboxed : string = unbox boxed
    printfn $"{unboxed}"
with
| :? System.InvalidCastException as ex ->
    printfn $"Error: {ex.Message}"

Attempting to unbox to an incompatible type throws an exception.

let unboxed : string = unbox boxed

This fails because the boxed value is an int, not a string.

λ dotnet fsi invalid.fsx
Error: Unable to cast object of type 'System.Int32' to type 'System.String'.

F# boxing performance

Boxing has performance costs due to heap allocation.

performance.fsx
#time "on"
printfn "Testing boxing performance"

let testBoxing count =
    let mutable sum = 0L
    for i in 1L..count do
        let boxed = box i
        sum <- sum + (unbox<int64> boxed) // Ensure actual work happens

    printfn "Boxing sum: %d" sum

testBoxing 100_000_000  // Use a smaller count for practical timing

#time "off"

#time "on"
printfn "Testing no boxing performance"

let testNoBoxing count =
    let mutable sum = 0L
    for i in 1L..count do
        sum <- sum + i  // Perform equivalent work without boxing

    printfn "No boxing sum: %d" sum

testNoBoxing 100_000_000  
#time "off"

In the first test, we box and unbox a value type in a loop. The second test performs the same operation without boxing. The difference in performance is significant.

let boxed = box i

Each boxing operation allocates memory on the heap.

λ dotnet fsi performance.fsx
Testing boxing performance
Boxing sum: 5000000050000000
Real: 00:00:00.902, CPU: 00:00:01.187, GC gen0: 112, gen1: 3, gen2: 2
Testing no boxing performance
No boxing sum: 5000000050000000
Real: 00:00:00.079, CPU: 00:00:00.062, GC gen0: 0, gen1: 0, gen2: 0

F# avoiding boxing

Use generics to avoid unnecessary boxing.

generic.fsx
let printValue (x: 'a) =
    printfn $"Value: {x}, Type: {typeof<'a>}"

printValue 42
printValue "hello"
printValue true

Generic functions handle value types without boxing.

let printValue (x: 'a) =

The generic parameter avoids boxing by working with the actual type.

λ dotnet fsi generic.fsx
Value: 42, Type: System.Int32
Value: hello, Type: System.String
Value: true, Type: System.Boolean

Boxing and unboxing are fundamental but costly operations in F#. Use them judiciously and prefer generics when possible to maintain performance.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.