Nushell
Get Nu!
Getting Started
  • The Nushell Book
  • Command Reference
  • Cookbook
  • Language Reference Guide
  • Contributing Guide
Blog
  • English
  • 中文
  • Deutsch
  • Français
  • Español
  • 日本語
  • Português do Brasil
  • Русский язык
  • 한국어
GitHub
Get Nu!
Getting Started
  • The Nushell Book
  • Command Reference
  • Cookbook
  • Language Reference Guide
  • Contributing Guide
Blog
  • English
  • 中文
  • Deutsch
  • Français
  • Español
  • 日本語
  • Português do Brasil
  • Русский язык
  • 한국어
GitHub
  • Language Reference Guide
    • Readme
    • Types in the Nu Language
      • Basic Types
        • Any
        • Boolean
        • Integer
        • Float
        • Filesize
        • Duration
        • Datetime
        • Range
        • String
        • Record
        • List
        • Table
        • Closure
        • Nothing
        • Binary
        • Glob
        • Cell-Path
      • Other Data Types

        • Types used only in command signatures
          • Path
        • Types which are not declarable
          • Error
          • CustomValue
          • Block
      • Type signatures
      • Commands that interact with types
    • Operators
    • Flow control
      • if/else
      • loop
      • while
      • match
      • try/catch
      • break
      • return
      • continue
    • Filters
      • each and par-each
      • Filters to select subsets of data
      • where and filter
      • Understanding the difference between get and select
    • Custom Commands
    • Declarations
    • Variable Scope
    • Strings and Text Formatting
    • Helpers and debugging commands
    • Pipelines
    • MIME Types for Nushell

Variable Scope

Nushell is lexically scoped. Every block ({ ... }) starts a new scope. A variable is visible from its declaration to the end of the block that contains it, including any blocks, closures and custom commands nested inside. The environment ($env, including the current directory) follows slightly different rules, described below.

See also: Declarations for let, mut and const, Environment - Scoping in the Book, and Nushell's Environment is Scoped.

Blocks

A variable declared inside a block is not visible after the block ends. Declaring a variable with the same name inside a block shadows the outer one only until the block ends:

let x = 1
if true {
  let x = 2
  print $"inside: ($x)"
}
print $"outside: ($x)"
# => inside: 2
# => outside: 1
if true { let inner = 1 }; $inner
# => Error: nu::parser::variable_not_found
# =>
# =>   × Variable not found.
# =>    ╭─[repl_entry #1:1:28]
# =>  1 │ if true { let inner = 1 }; $inner
# =>    ·                            ───┬──
# =>    ·                               ╰── variable not found.
# =>    ╰────

Variables are resolved while parsing, so a variable can only be used after its declaration. Each iteration of a loop gets a fresh scope, so a let in a loop body does not survive into the next iteration.

A block that belongs to if/else, match, for, while, loop or try runs in the scope of the surrounding code. It can therefore assign to a mut variable declared outside:

mut count = 1; if true { $count = 2 }; $count
# => 2

Closures Capture Values

A closure ({|args| ... }, and also the blocks given to commands such as each, where and do) captures the variables it uses when the closure is created. Shadowing the variable afterwards does not change what the closure sees:

let x = 1
let get_x = {|| $x }
let x = 2
do $get_x
# => 1

Because the captured value is a copy, a closure cannot capture a mut variable:

mut x = 1; let c = {|| $x }
# => Error: nu::parser::expected_keyword
# =>
# =>   × Capture of mutable variable.
# =>    ╭─[repl_entry #1:1:24]
# =>  1 │ mut x = 1; let c = {|| $x }
# =>    ·                        ─┬
# =>    ·                         ╰── capture of mutable variable
# =>    ╰────

A closure keeps its captured variables after the scope that declared them has ended:

let c = do { let secret = 42; {|| $secret } }; do $c
# => 42

Custom Commands

A custom command's body sees the variables in scope where the command is defined, not where it is called. Like a closure, it cannot capture mut variables. Parameters and variables declared in the body are local to the command.

let x = 1
def f [] { $x }
def g [] { let x = 5; f }
g
# => 1

def and alias definitions are scoped like variables: a command defined inside a block, closure or other command is not visible outside it. Unlike variables, definitions are available in their whole block, even before the line that defines them:

print (greet); def greet [] { 'defined later' }
# => defined later
def outer [] { def helper [] { 'helper' }; helper }; outer
# => helper

Environment Scope

Changes to $env, including the current directory changed with cd, stay visible after if/else, match, for, while, loop and try (including its catch and finally parts). They are discarded when a closure or custom command returns, unless the closure is run with do --env or the command is defined with def --env.

Where $env is changedVisible to the caller afterwards
if, match, loops, tryYes
do { ... }, each { ... }No
do --env { ... }Yes
def cmd [] { ... }No
def --env cmd [] { ... }Yes
$env.FOO = 'outer'
do { $env.FOO = 'inner'; print $env.FOO }
$env.FOO
# => inner
# => outer
def --env set-foo [] { $env.FOO = 'set in def --env' }
set-foo
$env.FOO
# => set in def --env
cd /tmp; if true { cd / }; pwd
# => /
cd /tmp; do { cd / }; pwd
# => /tmp

See Changing the Environment in a Custom Command in the Book for more on def --env.

Edit this page on GitHub
Contributors: NotTheDr01ds, Kieron Wilkinson, fdncred
Prev
Declarations
Next
Strings and Text Formatting