In Ruby, what can eval(code, some_binding) read, and why can't later code in your method use a local the string assigned?
answer
- the binding's self and locals
- locals fixed at parse time
- new locals live in the binding
- fresh scope per bare eval
- Binding#eval is the same call
basics
~20 sKernel#eval with a Binding runs the string in that binding's context: its self, its local variables and its constant scope. Locals the string creates stay in that binding, because the surrounding method's locals were fixed when it was parsed.
solid answer
~40 s`eval(code, b)` compiles `code` as if it were written where `b` was captured: `self`, local variables and constant lookup all come from that scope, even inside a method that has already returned. Without a binding, `eval` uses the caller's own scope. A local assigned inside the string does not become a local of the surrounding method, because Ruby decided which names are locals when it parsed that method; a later bare `tax` there is parsed as a method call and raises `NameError`. The new local lives in the binding's dynamic scope instead, so a later `eval("tax", b)` on the same `Binding` object finds it. Two `eval` calls without a binding do not share new locals. `b.eval(code)` is the same operation written on the binding.
code
ruby · 13 linesdef pricing_scope
price = 40
binding
end
scope = pricing_scope
eval("price * 2", scope) # => 80: reads the method's local
eval("tax = 8", scope)
eval("price + tax", scope) # => 48: same Binding keeps tax
scope.eval("tax") # => 8: Binding#eval is the same call
eval("discount = 5")
eval("discount") # NameError: no shared scope between bare evalsgo deeper
Recall that eval runs a string as Ruby code, and that passing a binding makes it run in the scope where the binding was captured.
Explain that locals are fixed at parse time, so a local created in eval stays in the binding, and trace the NameError that follows.
Recognise binding-based eval in templates and consoles, and replace it with explicit arguments wherever a scope does not need to be exposed.
Keep string evaluation behind a few reviewed tools, since each call site widens what a code reviewer must reason about.
## What Kernel#eval does `Kernel#eval(string, binding = nil, filename = nil, lineno = nil)` compiles the string as Ruby code at run time and runs it. The second argument decides **where** the code runs: - **No binding**: the code runs in the caller's own scope. It sees the caller's `self`, the caller's local variables and the caller's constant lookup. - **A `Binding`**: the code runs in the scope where that binding was captured, typically by calling `binding` inside a method or block. It sees *that* scope's `self`, locals and constants, even if the method that created the binding has long returned. The `Binding` object itself — how it is captured and what it keeps alive — is a closures topic. For `eval` the point is simply that it names a scope. ## Reading through a binding A binding is how template engines and consoles let text refer to variables that exist somewhere else. If a method assigns `price = 40` and returns `binding`, then `eval("price * 2", that_binding)` returns `80`, and `eval("self", that_binding)` returns the object the method ran on. Calling `binding.eval("price * 2")` does the same thing: `Binding#eval` forwards to `Kernel#eval` with itself as the scope. ## Why new locals do not leak out Ruby decides which bare names are local variables **when it parses** a method, not when it runs it. A name counts as a local only if an assignment to it appears earlier in the same scope. So in: ```ruby def total eval("tax = 8") tax end ``` the parser never saw `tax = ...` in the method body — it was hidden inside a string — so the final `tax` is compiled as a method call. At run time it raises `NameError` (undefined local variable or method). The assignment inside the string did create a local, but in a scope that exists only for the evaluated code. With an explicit binding, the rules are: 1. A local created by `eval("tax = 8", b)` is stored in the binding's dynamic scope. 2. A later `eval("tax", b)` **on the same `Binding` object** finds it. 3. Compiled code around the call still does not see it, for the parsing reason above. 4. Two `eval` calls **without** a binding do not share new locals: `eval("z = 1"); eval("z")` raises `NameError`. | Code | Result | |---|---| | `eval("price * 2", b)` where `b` captured `price = 40` | `80` | | `eval("tax = 8", b); eval("price + tax", b)` | `48` | | `eval("tax = 8", b); tax` in compiled code | `NameError` | | `eval("z = 1"); eval("z")` | `NameError` | Assigning to a local that **already exists** in the scope is different: `eval("price = 50", b)` updates the existing variable, and compiled code in that scope sees the new value, because the variable was already known when the scope was parsed. ## File, line and errors The third and fourth arguments set the file name and starting line reported for the evaluated code. When you pass a binding but no file name, the code does not inherit the binding's location; backtraces point at an `(eval at ...)` location naming where `eval` was called. Errors raised inside the string propagate to the caller like any other exception, and a malformed string raises `SyntaxError` at the call. ## Mistakes to avoid - Expecting a local assigned inside `eval` to be usable by the next line of the method. - Expecting two bare `eval` calls to share a local that the first one created. - Thinking a binding only carries local variables; it carries `self` and constant scope too. - Using `eval` where a plain method call, `send` or a hash lookup would do; string evaluation is slower to run, harder to read and dangerous with input. ## When eval with a binding is the right tool It is rare in application code. It appears in template engines that render text against a caller-supplied scope, in debugging consoles that evaluate what you type in a paused frame, and in tests of such tools. Everywhere else, pass values explicitly.
- If eval("price = 50", b) runs and price already existed where b was captured, does compiled code in that scope see 50?Yes. `price` was already a local when that scope was parsed, so the evaluated assignment updates the existing variable, and any compiled code in the scope that reads `price` afterwards sees `50`. Only names that did not exist at parse time stay confined to the binding.
- What does eval use as the scope when you pass no binding?The caller's own scope: its `self`, its local variables and its constant lookup. That is why `x = 10; eval("x + 1")` returns `11`. Locals the string creates still do not appear in the caller's compiled code.
saying these in an interview costs you the question
- A local assigned inside eval is available on the next line of the method
- Two eval calls without a binding share the locals they create
- A Binding carries only local variables, not self
- eval without a binding runs at the top level, not in the caller's scope
- Binding#eval evaluates differently from Kernel#eval with that binding