Un atelier Ruby · Édition d'ApprentissageA Ruby Workshop · Learning Edition

self, le receveur, et l'envoi de message self, the receiver, and the message send

Le code naïf croit que 1 + 2 est une opération câblée. En Ruby, c'est 1.+(2) — un message + envoyé à l'objet 1. Il n'y a pas de fonction libre, pas d'opérateur primitif : seulement des objets qui s'envoient des messages, et un receveur, fût-il implicite. Naive code thinks 1 + 2 is a wired-in operation. In Ruby it's 1.+(2) — a + message sent to the object 1. There are no free functions, no primitive operators: only objects sending each other messages, and a receiver, even an implicit one.

AudienceAudience
Dev venant d'un langage statique, prêt à raisonner en messages Dev from a static language, ready to reason in messages
Format
Self-paced
ChapitresChapters
5
Date
Déc 2025 Dec 2025
≈ 18 min ●○○○ ObjetMessageself
SommaireContents ·
01CadrageFraming3 min

Même 1 + 2 est un message. Trouve le receveur.Even 1 + 2 is a message. Find the receiver.

Une addition que tout le monde tape le premier jour. Elle n'a rien d'une opération câblée dans le langage : c'est la méthode + appelée sur l'objet 1, avec 2 pour argument. Trois caractères, et tout le modèle de Ruby tient dedans : il n'y a pas de calcul, seulement des objets qui s'envoient des messages.An addition everyone types on day one. It is nothing like an operation wired into the language: it's the method + called on the object 1, with 2 as its argument. Three characters, and all of Ruby's model fits inside: there is no computation, only objects sending each other messages.

Le code qu'on croit primitifThe code we think is primitive
1 + 2          # ce que tout le monde écrit
1.+(2)         # ce que Ruby exécute, vraiment
# => 3         # un message + envoyé à l'objet 1, avec 2 en argument
L'anatomie d'un envoiAnatomy of a send
1
receveurreceiver
.
envoisend
+
messagemessage
(
2
argumentargument
)
retournereturns
3
objet retourreturned object

Il n'y a pas d'« opérateur + » dans Ruby. Il y a l'objet 1 qui reçoit le message + et décide, lui, quoi en faire.There is no "+ operator" in Ruby. There is the object 1 that receives the message + and decides, itself, what to do with it.

Le réflexe du volumeThe volume's reflex

« À quel objet est-ce que j'envoie ce message — et qu'est-ce qu'il en fait ? » Lis obj.foo non pas comme « accès à un champ » mais comme « j'envoie le message foo à obj, qui est libre d'y répondre comme il veut ». L'addition, l'indexation, l'appel de méthode : autant d'envois de messages."Which object am I sending this message to — and what does it do with it?" Read obj.foo not as "field access" but as "I send the message foo to obj, which is free to answer however it likes." Addition, indexing, method calls: all message sends.

Pourquoi ce numéro ouvre la série.Why this issue opens the series.

Tout le reste de Ruby — les blocs, les mixins, le duck typing, la métaprogrammation — est une conséquence de cette règle. On ne commence pas par la syntaxe des classes : on commence par une addition qu'on croyait primitive et qui était un message. Comprends l'envoi, et la « magie » de Ruby cesse d'en être.Everything else in Ruby — blocks, mixins, duck typing, metaprogramming — is a consequence of this rule. We don't start with class syntax: we start with an addition you thought was primitive and was actually a message. Understand the send, and Ruby's 'magic' stops being magic.

02La surpriseThe surprise4 min

En Java, une fonction existe seule. En Ruby, il n'y en a aucune.In Java, a function stands alone. In Ruby, there are none.

Quand on vient d'un langage statique, « appeler une fonction » et « appeler une méthode » sont deux gestes distincts, et + est un opérateur du langage. Ruby n'a qu'un seul geste : envoyer un message à un objet. Même la fonction que tu crois définir « toute seule » devient une méthode d'un receveur — self, l'objet courant.Coming from a static language, 'calling a function' and 'calling a method' are two distinct gestures, and + is a language operator. Ruby has only one gesture: sending a message to an object. Even the function you think you define 'on its own' becomes a method of a receiver — self, the current object.

Java — fonctions libres & opérateurs câblésJava — free functions & wired operators
// Java : une fonction "libre", attachée à une classe
static int square(int x) { return x * x; }

square(5);        // appel direct : pas de receveur
Math.max(1, 2);   // + est un opérateur primitif, pas une méthode

Deux catégories d'appels coexistent : la fonction qu'on invoque sans receveur, et la méthode sur un objet. Et +, * sont des opérateurs du langage, pas des messages — on ne peut pas les redéfinir.Two call categories coexist: the function you invoke with no receiver, and the method on an object. And +, * are language operators, not messages — you cannot redefine them.

La « fonction libre » d'un autre langage, Ruby la pose toujours sur un objet. Il n'y a jamais que des receveurs.The "free function" of another language, Ruby always lands on an object. There are only ever receivers.
03La mécaniqueThe mechanics5 min

Le point . n'est qu'un raccourci. Dessous : send.The dot . is just shorthand. Underneath: send.

Si tout appel est un envoi, alors le nom de la méthode n'est pas figé dans la syntaxe : c'est une donnée. obj.foo et obj.send(:foo) sont le même geste, et le second laisse choisir le message à l'exécution. Les opérateurs ne sont que du sucre par-dessus ce même envoi — d'où le fait qu'on puisse les définir soi-même.If every call is a send, then the method name isn't frozen into syntax: it's data. obj.foo and obj.send(:foo) are the same gesture, and the second lets you pick the message at runtime. Operators are mere sugar over that same send — which is exactly why you can define them yourself.

Le message est une donnéeThe message is data
"hello".upcase          # => "HELLO"
"hello".send(:upcase)   # => "HELLO" — strictement le même envoi
"hello".send("upcase")  # => "HELLO" — le message peut être nommé à l'exécution

msg = :upcase
"hello".send(msg)       # => "HELLO" — le message est une donnée comme une autre
Le sucre syntaxique, dépliéThe syntactic sugar, unwrapped
a + ba.+(b)
a[i]a.[](i)
a[i] = xa.[]=(i, x)
-aa.-@
a.name = va.name=(v)
a == ba.==(b)

Chaque ligne de gauche est la ligne de droite. L'opérateur, l'indexation, l'affectation d'attribut : des noms de méthodes comme les autres.Each line on the left is the line on the right. Operator, indexing, attribute assignment: method names like any other.

Les méthodes aussi sont des objets.Methods are objects too.

"hello".method(:upcase) renvoie un objet Method qu'on peut stocker, passer, appeler avec .call. On reverra ça au Vol 1 № 2 : en Ruby, même ce qui « fait » est un objet."hello".method(:upcase) returns a Method object you can store, pass around, call with .call. We'll revisit this in Vol 1 № 2: in Ruby, even what "acts" is an object.

Définir un opérateur, c'est définir un messageDefining an operator is defining a message
class Money
  attr_reader :cents
  def initialize(cents) = @cents = cents

  def +(other)                      # on DÉFINIT le message +
    Money.new(@cents + other.cents)
  end

  def to_s = format("%.2f €", @cents / 100.0)
end

Money.new(150) + Money.new(99)   # => 2.49 € — Money#+ a répondu

On n'a pas « surchargé un opérateur » au sens d'un langage statique : on a simplement défini la méthode + sur Money. Quand Ruby voit argent + autre, il envoie le message + à argent — qui, cette fois, sait y répondre.We didn't "overload an operator" in the static-language sense: we simply defined the method + on Money. When Ruby sees money + other, it sends the message + to money — which, this time, knows how to answer.

04Le piègeThe trap4 min

send n'est pas un bouton « malin ». C'est une clé passe-partout.send is not a "clever" button. It's a master key.

Comprendre que tout est un envoi donne envie de tout passer par send. Mauvais réflexe. send ignore la confidentialité — il ouvre les méthodes privées — et un send piloté par une entrée externe laisse l'utilisateur choisir quel message envoyer. La puissance de l'envoi dynamique se paie : moins d'intention lisible, et une porte grande ouverte si le message vient de dehors.Realizing everything is a send makes you want to route everything through send. Bad reflex. send ignores privacy — it opens private methods — and a send driven by external input lets the user choose which message to send. The power of dynamic dispatch has a price: less readable intent, and a wide-open door if the message comes from outside.

send franchit le mur du privé ; public_send nonsend breaks the privacy wall; public_send doesn't
class Account
  def name = "Ada"
  private
  def secret_pin = "4242"
end

acc = Account.new
acc.name                  # => "Ada"
acc.secret_pin            # NoMethodError: private method 'secret_pin'
acc.send(:secret_pin)     # => "4242" — send IGNORE la confidentialité
acc.public_send(:secret_pin) # NoMethodError — public_send la RESPECTE
L'indirection gratuiteThe needless indirection
# Le réflexe "malin" : tout passer par send
user.send(:name)          # … alors que user.name disait la même chose
record.send(:save)        # une indirection gratuite qui masque l'intention

Si le message est connu à l'écriture, écris-le. user.name dit l'intention ; user.send(:name) la cache derrière une réflexion dont personne n'avait besoin — et qu'aucun grep ne retrouvera.If the message is known at write-time, write it. user.name states the intent; user.send(:name) hides it behind reflection nobody needed — and that no grep will find.

Le contre-exemple obligatoire.The mandatory counter-example.

Atteindre send pour « faire le malin » là où un appel direct disait la même chose : on a perdu la lisibilité ET la garde du privé, pour rien. send se mérite — quand le message doit vraiment être calculé.Reaching for send to 'be clever' where a direct call said the same thing: you lost readability AND the privacy guard, for nothing. send is earned — only when the message genuinely must be computed.

Quand le message vient de l'extérieurWhen the message comes from outside
# Le trou de sécurité : un message piloté par l'entrée utilisateur
obj.send(params[:action])      # params[:action] = "destroy_all" ? "send"-icide
                               # send atteint AUSSI les méthodes privées

# Le correctif : public_send + liste blanche explicite
ALLOWED = %i[show index search].freeze
action = params[:action].to_sym
raise ArgumentError unless ALLOWED.include?(action)
obj.public_send(action)        # message calculé, mais borné et public

Un send dont le nom vient de params laisse l'utilisateur appeler n'importe quelle méthode de l'objet, privées comprises. Le correctif n'est pas « ne jamais calculer un message », c'est public_send + une liste blanche : un message dynamique, mais borné et public.A send whose name comes from params lets the user call any method on the object, private ones included. The fix isn't "never compute a message," it's public_send + an allowlist: a dynamic message, but bounded and public.

05Bilan & éditoWrap-up & editorial2 min

Une seule grammaire : un objet, un message.One grammar only: an object, a message.

L'envoi de message n'est pas un détail d'implémentation à oublier : c'est la pièce qui fait tenir tout l'édifice. Une fois le réflexe en place — chercher le receveur avant la méthode — le reste de Ruby se lit comme ses conséquences.The message send isn't an implementation detail to forget: it's the keystone that holds the whole structure. Once the reflex is in place — find the receiver before the method — the rest of Ruby reads as its consequences.

Ce que tu écrisWhat you writeCe que Ruby envoieWhat Ruby sendsLe réflexeThe reflex
Un opérateurAn operator1 + 2 est 1.+(2) : un message + à l'objet 1.1 + 2 is 1.+(2): a + message to the object 1.Aucun opérateur n'est câblé — donc tu peux les définir sur tes objets.No operator is wired in — so you can define them on your objects.
Une « fonction »A 'function'puts "hi" est self.puts("hi") : un message au receveur implicite.puts "hi" is self.puts("hi"): a message to the implicit receiver.Il n'y a pas de fonction libre. Cherche self : il y a toujours un receveur.There is no free function. Look for self: there is always a receiver.
Un appel directA direct callobj.foo est obj.send(:foo) : le nom du message est une donnée.obj.foo is obj.send(:foo): the message name is data.Écris le message quand tu le connais. send se mérite — quand il doit être calculé.Write the message when you know it. send is earned — when it must be computed.
Un message calculéA computed messageobj.public_send(action) : un envoi dynamique, mais qui respecte le privé.obj.public_send(action): a dynamic send that respects privacy.Jamais send sur une entrée externe. public_send + liste blanche, sinon n'importe quelle méthode est appelable.Never send on external input. public_send + an allowlist, else any method is callable.
NoMethodError n'est pas une punition. C'est l'objet qui te dit : je n'ai pas de réponse à ce message — tu t'es trompé de receveur, ou de message.NoMethodError is not a punishment. It's the object telling you: I have no answer to this message — you got the receiver, or the message, wrong.
Mot de l'éditeurFrom the editor

La « magie » de Ruby n'en est pas.Ruby's "magic" isn't.

On présente souvent Ruby comme un langage « magique », plein de méthodes qui apparaissent de nulle part. C'est l'inverse d'une magie : c'est une seule règle, appliquée sans exception. Tout est un objet, et tout calcul est l'envoi d'un message à un objet qui décide d'y répondre. Tant qu'on collectionne les méthodes une par une, on subit cette « magie » ; le jour où l'on voit, derrière chaque ligne, un message qui part vers un receveur, on cesse de mémoriser des incantations — on lit le langage.Ruby is often pitched as a 'magic' language, full of methods that appear from nowhere. It's the opposite of magic: it's one rule, applied without exception. Everything is an object, and every computation is a message sent to an object that decides whether to answer. As long as you collect methods one by one, you're subject to that 'magic'; the day you see, behind each line, a message traveling to a receiver, you stop memorizing incantations — you read the language.

Prochain numéro : tout est objet — vraiment tout. nil, true, les nombres, et les classes elles-mêmes. Si tout reçoit des messages, alors tout est un objet ; on ira voir jusqu'où ça tient.Next issue: everything is an object — truly everything. nil, true, the numbers, and the classes themselves. If everything receives messages, then everything is an object; we'll see how far that holds.

Retour au kiosqueBack to newsstand