Vol. 1 · № 03 · Un atelier RubyA Ruby workshop

nil n'est pas null nil is not null

On traduit nil par null et on attend un crash de pointeur. Erreur : nil est l'unique instance de NilClass, un objet présent qui répond aux messages. Son NoMethodError n'est pas un segfault — c'est un receveur qui décline un message qu'il ne connaît pas. Remonte à la source du nil. We translate nil as null and brace for a pointer crash. Wrong: nil is the sole instance of NilClass, a present object that answers messages. Its NoMethodError isn't a segfault — it's a receiver declining a message it doesn't know. Trace back to the source of the nil.

AudienceAudience
Dev habitué à null, prêt à voir nil comme un objet Dev used to null, ready to see nil as an object
Format
Self-paced
ChapitresChapters
5
Date
Jan 2026 Jan 2026
≈ 18 min ●○○○ ObjetnilTradeoff
SommaireContents ·
№ 01CadrageFraming3 min

nil n'est pas l'absence d'objet. C'est un objet bien présent.nil is not the absence of an object. It's an object, fully present.

On a établi que tout est objet, sans exception. nil est le cas qui le prouve jusqu'au bout. Là où d'autres langages ont un null qui signifie « rien ici » — et qui plante quand on l'interroge — Ruby a nil : l'unique instance de NilClass, un objet qui a une classe et répond à des messages. Son fameux NoMethodError n'est pas un crash : c'est un objet qui décline poliment un message qu'il ne connaît pas.We established that everything is an object, no exceptions. nil is the case that proves it all the way down. Where other languages have a null meaning 'nothing here' — that crashes when queried — Ruby has nil: the sole instance of NilClass, an object that has a class and answers messages. Its infamous NoMethodError is no crash: it's an object politely declining a message it doesn't know.

nil répond — jusqu'à ce qu'on lui parle malnil answers — until you message it wrong
nil.class            # => NilClass
nil.nil?             # => true
nil.to_s             # => ""      — nil répond, et utilement
nil.to_a             # => []
nil.inspect          # => "nil"

nil.upcase           # NoMethodError: undefined method 'upcase' for nil
# ↑ pas un crash mémoire : un objet qui dit « je n'ai pas ce message »
L'anatomie d'un NoMethodErrorAnatomy of a NoMethodError
nil
objet présentobject, present
.
messagemessage
upcase
inconnu de luiunknown to it
⟶
il déclineit declines
NoMethodError
…for nil…for nil

Le message d'erreur dit for nil — il y avait bien un destinataire, nil, un objet de classe NilClass. Il n'a juste pas la méthode upcase.The error says for nil — there was indeed a receiver, nil, an object of class NilClass. It simply lacks the upcase method.

Le réflexe du volumeThe volume's reflex

« À quel objet est-ce que je parle ? » s'applique même à nil. Quand tu vois NoMethodError for nil, la question n'est pas « pourquoi ça a planté » mais « pourquoi ce receveur est-il nil au lieu de l'objet que j'attendais ? ». Le bug n'est jamais dans nil — il est en amont, là où nil s'est glissé."Which object am I talking to?" applies even to nil. When you see NoMethodError for nil, the question isn't "why did it crash" but "why is this receiver nil instead of the object I expected?". The bug is never in nil — it's upstream, where nil slipped in.

Pourquoi nil clôt le Vol 1.Why nil closes Vol 1.

Le № 1 a posé l'envoi de message ; le № 2, que tout est objet. nil est le test ultime des deux : l'objet qu'on prend le plus souvent pour un « rien » est, lui aussi, un receveur. Comprendre nil, c'est cesser de craindre le NoMethodError et commencer à le lire comme une adresse : remonte à la source du nil.Issue № 1 set up the message send; № 2, that everything is an object. nil is the ultimate test of both: the object most often mistaken for 'nothing' is, too, a receiver. To understand nil is to stop fearing NoMethodError and start reading it as an address: trace back to the source of the nil.

№ 02La surpriseThe surprise4 min

null plante parce qu'il n'y a rien. nil répond parce qu'il y a quelque chose.null crashes because there's nothing. nil answers because there's something.

La confusion vient du nom : on traduit nil par null et on s'attend au même comportement. Mais ce sont deux choses opposées. null est l'absence de référence — le déréférencer plante car il n'y a rien derrière. nil est un objet bien réel : on peut l'interroger, le convertir, le tester. Le NoMethodError n'est pas le NullPointerException — c'est un objet présent qui ne connaît pas un message.The confusion comes from the name: we translate nil as null and expect the same behavior. But they are opposites. null is the absence of a reference — dereferencing it crashes because there's nothing behind it. nil is a genuine object: you can query it, convert it, test it. NoMethodError is not NullPointerException — it's a present object that doesn't know a message.

Java — déréférencer le videJava — dereferencing the void
// Java : null est l'ABSENCE d'objet
String name = lookup();   // renvoie null si rien trouvé
name.length();            // NullPointerException — on déréférence le vide

// null n'a pas de classe, pas de méthodes, pas d'identité :
null.getClass();          // ne compile même pas
// le crash vient de ce qu'il n'y a RIEN derrière la référence

null n'a ni classe, ni méthode, ni identité — c'est une référence qui ne pointe sur rien. Le crash n'est pas « ce message est inconnu », c'est « il n'y a aucun objet ici pour recevoir quoi que ce soit ».null has no class, no method, no identity — it's a reference pointing at nothing. The crash isn't "this message is unknown," it's "there is no object here to receive anything."

Traduire nil par « null », c'est attendre un crash là où Ruby t'offre un objet. Le nom trompe ; la classe ne ment pas.Translating nil as "null" means expecting a crash where Ruby hands you an object. The name misleads; the class doesn't lie.
№ 03La mécaniqueThe mechanics5 min

nil a un protocole. Et il n'est faux qu'avec false.nil has a protocol. And it's falsy only alongside false.

Puisque nil est un objet, il a des méthodes — et elles sont pensées pour qu'il se fonde dans les conversions sans planter. Deux choses à intérioriser : en Ruby, seuls nil et false sont faux (0, la chaîne vide et le tableau vide sont tous vrais), et l'opérateur &. permet d'envoyer un message « seulement si le receveur n'est pas nil ». Deux outils précis — qu'on verra ensuite ne pas sur-utiliser.Since nil is an object, it has methods — designed so it blends into conversions without crashing. Two things to internalize: in Ruby, only nil and false are falsy (0, the empty string and the empty array are all truthy), and the &. operator sends a message 'only if the receiver isn't nil'. Two precise tools — which we'll then see not to overuse.

Le protocole de conversion de nilnil's conversion protocol
nil.to_s→""
nil.to_a→[]
nil.to_h→{}
nil.to_i→0
nil.inspect→"nil"
nil.nil?→true

Chaque conversion renvoie l'« élément neutre » de son type : chaîne vide, tableau vide, hash vide, zéro. C'est ce qui laisse "x" + nil.to_s marcher sans test préalable.Each conversion returns the "neutral element" of its type: empty string, empty array, empty hash, zero. That's what lets "x" + nil.to_s work without a prior test.

nil est un singleton.nil is a singleton.

Il n'existe qu'un seul objet nil : nil.object_id est toujours le même. Inutile d'en « créer » un — quand une méthode n'a rien à renvoyer, elle renvoie ce nil. C'est l'absence, représentée par un objet unique et partagé.There is only one nil object: nil.object_id is always the same. No need to "create" one — when a method has nothing to return, it returns this nil. It's absence, represented by a single shared object.

« seulement si pas nil » : la navigation sûre'only if not nil': safe navigation
user&.account&.balance      # si user est nil → nil, sans NoMethodError

# &. n'est que du sucre : a&.b équivaut à
a.nil? ? nil : a.b

# à ne pas confondre avec || (valeur par défaut) :
name = user.name || "anonyme"   # "" reste "" ; seuls nil/false déclenchent le défaut
Et la règle de vérité, contre-intuitiveAnd the truthiness rule, counter-intuitive
# En Ruby, SEULS nil et false sont faux. Tout le reste est vrai.
!!0        # => true   — zéro est vrai
!!""       # => true   — la chaîne vide est vraie
!![]       # => true   — le tableau vide est vrai
!!nil      # => false
!!false    # => false
# donc "if x" teste la présence (nil/false), pas le "vide"

Venant d'un langage où 0 ou la chaîne vide sont « faux », c'est un piège classique : ici if count est vrai même quand count vaut 0. if teste la présence (ni nil ni false), jamais le « vide ».Coming from a language where 0 or the empty string are "falsy," this is a classic trap: here if count is true even when count is 0. if tests presence (neither nil nor false), never "emptiness."

№ 04Le piègeThe trap4 min

Le &. en chaîne ne règle pas le nil. Il le déplace.A chain of &. doesn't fix the nil. It relocates it.

Sitôt qu'on découvre &., on est tenté d'en mettre partout « pour ne plus jamais voir de NoMethodError ». Mais &. ne supprime pas le nil : il le laisse passer. Une chaîne de &. renvoie nil au moindre maillon absent — et ce nil continue son chemin jusqu'à exploser ailleurs, à mille lignes de la vraie cause. On n'a pas évité le bug ; on l'a rendu plus difficile à trouver.The moment you discover &., you're tempted to sprinkle it everywhere 'to never see NoMethodError again'. But &. doesn't remove the nil: it lets it through. A chain of &. returns nil at the first missing link — and that nil travels on until it blows up elsewhere, a thousand lines from the real cause. You didn't avoid the bug; you made it harder to find.

Le &. en chaîne qui masque la causeThe chained &. that hides the cause
# Le réflexe « par sécurité » : un &. à chaque maillon
total = user&.cart&.items&.sum(&:price)

# Si user existe mais cart est nil (un bug !), total devient nil…
# … et le nil voyage. Trois écrans plus loin :
"Total : #{total.round(2)} €"   # NoMethodError: undefined 'round' for nil
# l'erreur surgit LOIN de la cause — un cart qui n'aurait jamais dû être nil
Décider à la frontière, pas en chaîneDecide at the boundary, not in a chain
# Le correctif : ne pas avaler le nil — décider à la frontière

# 1. si l'absence est un BUG : garantis l'invariant, échoue tôt et clair
cart = user.cart or raise "panier manquant pour #{user.id}"
total = cart.items.sum(&:price)

# 2. si l'absence est LÉGITIME : un défaut explicite, une seule fois
items = user.cart&.items || []
total = items.sum(&:price)        # 0, pas nil — le reste du code respire

# 3. mieux : un objet qui se comporte comme un panier vide (Null Object)
cart = user.cart || EmptyCart.new
Le contre-exemple obligatoire.The mandatory counter-example.

Empiler les &. « par sécurité » revient à dire « je ne sais pas si cet objet existe, et je préfère ne pas savoir ». La question juste est : ce nil est-il un bug (alors échoue tôt, avec un message qui pointe la cause) ou un cas légitime (alors donne un défaut explicite, une fois) ? &. répond « ni l'un ni l'autre » — et c'est presque toujours faux.Stacking &. "for safety" amounts to saying "I don't know whether this object exists, and I'd rather not know." The right question is: is this nil a bug (then fail early, with a message pointing at the cause) or a legitimate case (then give an explicit default, once)? &. answers "neither" — and that's almost always wrong.

Chaque &. que tu ajoutes est un aveu : je ne sais pas qui a mis nil ici. Va le découvrir — ne le repousse pas plus loin.Every &. you add is an admission: I don't know who put nil here. Go find out — don't push it further down.
№ 05Bilan & éditoWrap-up & editorial2 min

Le nil n'est pas l'ennemi. C'est une adresse.nil isn't the enemy. It's an address.

nil ferme le Vol 1 parce qu'il est le test ultime de sa thèse : même « rien » est un objet. Une fois qu'on cesse de le confondre avec null, le NoMethodError change de sens — ce n'est plus un crash à éviter à coups de &., c'est un panneau qui indique où remonter.nil closes Vol 1 because it's the ultimate test of its thesis: even 'nothing' is an object. Once you stop confusing it with null, NoMethodError changes meaning — it's no longer a crash to dodge with &., it's a sign pointing where to trace back.

Ce qu'on croitWhat you thinkCe que nil estWhat nil isLe réflexeThe reflex
« nil = null »'nil = null'nil.class → NilClass. Un objet, l'unique instance de sa classe.nil.class → NilClass. An object, the sole instance of its class.Ne l'attends pas comme un crash de pointeur. C'est un receveur présent.Don't expect a pointer crash. It's a present receiver.
« nil plante »'nil crashes'NoMethodError … for nil : l'objet existe, il décline juste ce message.NoMethodError … for nil: the object exists, it just declines this message.Lis l'erreur comme une adresse : pourquoi le receveur est-il nil ?Read the error as an address: why is the receiver nil?
« nil est vide »'nil is empty'nil.to_s → "", nil.to_a → []. Un protocole de conversion utile.nil.to_s → "", nil.to_a → []. A useful conversion protocol.Seuls nil et false sont faux. 0, "", [] sont vrais.Only nil and false are falsy. 0, "", [] are truthy.
« &. me protège »'&. protects me'a&.b renvoie nil si a est nil — le nil passe, il ne disparaît pas.a&.b returns nil if a is nil — the nil passes through, it doesn't vanish.Décide à la frontière : bug → échoue tôt ; cas légitime → défaut explicite.Decide at the boundary: bug → fail early; legitimate → explicit default.
undefined method for nil ne dit pas « ça a planté ». Ça dit : tu attendais un objet, tu as reçu nil — remonte voir pourquoi.undefined method for nil doesn't say "it crashed." It says: you expected an object, you got nil — go back and see why.
Mot de l'éditeurFrom the editor

Fin du Vol 1 : une idée, tenue jusqu'au bout.End of Vol 1: one idea, held all the way.

Trois numéros pour une seule phrase : en Ruby, tout est un objet, et tout calcul est l'envoi d'un message à un objet qui décide d'y répondre. L'addition était un message (№ 1) ; le receveur pouvait être n'importe quoi, classe comprise (№ 2) ; et même nil, qu'on prend pour un vide, est un objet qu'on interroge (№ 3). Aucun cas particulier n'a résisté. C'est ça, le socle : non pas une collection de règles, mais une seule, sans exception — et c'est exactement ce qui rend lisible tout ce qui vient après.Three issues for a single sentence: in Ruby, everything is an object, and every computation is a message sent to an object that decides whether to answer. The addition was a message (№ 1); the receiver could be anything, classes included (№ 2); and even nil, mistaken for emptiness, is an object you query (№ 3). No special case held out. That's the bedrock: not a collection of rules, but one, without exception — and that's precisely what makes everything that follows readable.

Prochain volume : les blocs. Maintenant qu'on sait qu'on envoie des messages à des objets, on va confier à ces objets un morceau de code — un bloc — et les laisser décider quand l'exécuter. C'est l'inversion de contrôle au cœur de each, de map, et de tout le style de Ruby.Next volume: blocks. Now that we know we send messages to objects, we'll hand those objects a piece of code — a block — and let them decide when to run it. That's the inversion of control at the heart of each, of map, and of Ruby's whole style.