Linguaggi di programmazione

Null safety

I call it my billion-dollar mistake. It was the invention of the null reference in 1965.

La frase è di Tony Hoare, in un discorso a una conferenza software nel 1965, riferita all'introduzione del valore null come riferimento agli oggetti nel linguaggio Algol W.

Eppure quello stesso valore null fu poi adottato da molti altri linguaggi orientati agli oggetti, tra i quali C++, Java, C# e via dicendo. L'effetto è stato di avere sistemi software che generavano errori imprevisti quando cercavano di eseguire operazioni su questo valore.

In anni recenti, in vari linguaggi, è stato finalmente posto rimedio a questa lacuna, con una gestione dei tipi che distingue in modo chiaro tra variabili nullabili e non. La convenzione sintattica più utilizzata è l'apposizione di un ? a seguito del nome del tipo.

Per comodità, vengono di solito messi a disposizione anche due operatori, ? e ??, che permettono rispettivamente di propagare il valore null al risultato dell'operazione, e di sostituire il valore null con un valore di default.


Dart

Ecco un semplice spezzone di codice Dart, per illustrare come gestisce i valori null un linguaggio null-safe:

// This variable cannot be null:
String name = 'Alice';
name = null; // Compile-time error

// This variable can be null (note the '?'):
String? nickname;
nickname = null; // OK
String upper = nickname.toUpperCase(); // Compile-time error
String? upperOrNull = nickname?.toUpperCase(); // OK, result is null
String nicknameOrDefault = nickname ?? "No nickname"; // OK, result is "No nickname"

// Non-nullable integer:
int age = 30;
age = null; // Compile-time error

// Nullable integer:
int? luckyNumber;
luckyNumber = null; // OK
int doubled = (luckyNumber ?? 0) * 2; // OK, uses 0 if luckyNumber is null

In sostanza ci sono due regole:

  • Dato di tipo non nullabile: non è permesso che abbia valore null;
  • Dato di tipo nullabile: non è possibile utilizzarlo senza le opportune precauzioni.

Queste regole vengono verificate già al momento della stesura del codice, o come si usa dire a tempo di compilazione.


C#

In C# la gestione dei tipi nullabili è del tutto analoga, con un'eccezione: si fa distinzione tra tipi scalari e oggetti. Quindi, benché questo fatto sia spesso nascosto dall'utilizzo dell'operatore ??, per un tipo scalare la nullabilità può necessitare l'utilizzo delle proprietà HasValue e Value della variabile:

int? luckyNumber = null;

if (luckyNumber.HasValue)
    Console.WriteLine($"Your lucky number is {luckyNumber.Value}");
else
    Console.WriteLine("You don't have a lucky number set.");

// Assign a value
luckyNumber = 7;

if (luckyNumber.HasValue)
    Console.WriteLine($"Your lucky number is {luckyNumber.Value}");

Questo si riflette anche su come i tipi sono gestiti attraverso il meccanismo della reflection:

class MyClass
{
    public string Name { get; set; } = "Alice";
    public string? NullableName { get; set; } = null;
    public int Age { get; set; } = 30;
    public int? NullableAge { get; set; } = null;
}

static void TestNullable()
{
    var myClassType = typeof(MyClass);
    var properties = myClassType.GetProperties();
    foreach (var property in properties)
        Console.WriteLine($"{property.Name}: {property.PropertyType}");
}

Si ottengono risultati diversi per gli oggetti e gli scalari:

Name: System.String
NullableName: System.String
Age: System.Int32
NullableAge: System.Nullable`1[System.Int32]


Dart

L'esempio precedente, tradotto in Dart, utilizzando il meccanismo dell'introspection (package mirror), rivela una gestione dei tipi nullabili più elegante e simmetrica:

class MyClass {
  String name = "Alice";
  String? nullableName;
  int age = 30;
  int? nullableAge;
}

void testNullable() {
  // Get the class type of MyClass
  var myClassMirror = reflectClass(MyClass);

  // Iterate over the fields (instance variables) of MyClass
  myClassMirror.declarations.forEach((key, declaration) {
    if (declaration is VariableMirror) {
      var fieldName = MirrorSystem.getName(key);
      var fieldType = MirrorSystem.getName(declaration.type.simpleName);
      print("$fieldName: $fieldType");
    }
  });
}

Si ottengono risultati anagoghi per gli oggetti e per gli scalari:

name: String
nullableName: String
age: int
nullableAge: int


Giorgio Barchiesi
Albo degli Ingegneri Sez. A, N. 4027 della Prov. di Trento
P.IVA 02370260222, C.F. BRC GRG 58L26 C794R

Copyright © 2015-2024 Giorgio Barchiesi - Tutti i diritti riservati