When choosing a language, platform, or technology, no doubt the question will arise: Static or Dynamic?

The software engineering debate is far from settled. With type systems specifying the type of a variable, but not the contents, does the value exist or is it overstated?

Set of Possible Inputs

Given any common programming task, whether a REST API, GraphQL, parsing a csv, or otherwise, most programs operate on unpredictable input. These examples use a simple integer, as static typing for Range Types is unimplemented in Rust, and existing types don’t cover other more complex scenarios.

Reading an Integer from Input in Rust

use std::io;

fn read_int() {
    let mut input: i64;
    match io::stdin().read_line(&mut input) {
        Ok(n) => {
            println!("{} was successfully read", input);
        }
        Err(error) => println!("error: {}", error),
    }
}

The above doesn’t compile:

expected struct `std::string::String`, found `i64`

Although Rust is statically typed and given information on the source type and desired type, it is not able to compile the expression. In this case the programmer does receive information about the error, which is helpful, but this category of error does not exist in dynamic languages.

Requiring the programmer to explicitly convert the integer to a string can be considered a feature, but when taking the time to specify the input of String and output of i64 the operation could be performed without additional toil.

The programmer needs to understand the inputs and outputs of functions regardless of the type signature.

Reading an Integer from a String from Input in Rust

use std::io;

fn read_int() -> Option<i64> {
    let mut input = String::new();
    match io::stdin().read_line(&mut input) {
        Ok(n) => return match input.trim().parse::<i64>() {
            Ok(i) => Some(i),
            Err(e) => None,
        },
        Err(error) => return None,
    }
}

fn main() {
    match read_int() {
        Some(x) => println!("x = {}", x),
        None => println!("No integers here")
    }
}

This example compiles and given a input of “5” prints “x = 5”, and an input of “char” prints “No integers here”. The Option<i64> type allows the programmer to check if it returned a value or not (not to be confused with Rust’s Result type, similar but different), which is comparable to Clojure’s nil in this case.

Reading an Integer from Input in Clojure

(defn read-int []
    (try (println "x = ", (Integer/parseInt (read-line)))
        (catch Exception e (println "No integers here"))))

(defn -main []
    (read-int))

This compiles and given a input of “5” prints “x = 5”, and a input of “char” prints “No integers here”. Clojure uses the type Java.lang.Integer for x, as returned by Integer/parseInt. Although not a statically typed language, the type information is available, and the function rejects invalid runtime values.

Comparison

Both static and dynamic implementations accomplish the same task for integer and string inputs. In real world systems, inputs are almost never defined prior to execution, or validated by the compiler. Systems typically operate on records provided by databases, caches, and various external resources which can change and mutate in many unexpected ways.

Static typing appears to be the most valuable in function signatures, to easily ascertain what is expected and returned by a function. However as demonstrated with the dynamically typed code, a function called parseInt returns a integer, which is to be expected when methods are named well.

Statically typed systems allow for any name, or mis-naming of variables, which mean that function signatures could easily introduce bugs despite the presence of a type signature. Naming is critical in both static and dynamic type systems.

The value of static typing is largely non-differentiated when using unpredictable data sources (json, user input etc). Dynamic languages lead to succinct code, quicker development cycles and less code surface area for defects to be introduced.

“In static languages, if it compiles its correct” is inaccurate when considering the values of potential input, logic which does not intersect with types, incorrect usage of types, and the overhead required to maintain them.

In both cases, input sanitization and type conversions are not automatic and the compiler cannot analyze runtime inputs.