Polyglot
Languages MATLAB scope
MATLAB § scope

Workspace and scope

MATLAB’s scoping discipline is unusual and, once internalised, is the substance of how a working MATLAB programmer reasons about state. There are three principal scopes — the base workspace (the interactive REPL), the function workspace (one per call), and persistent and global workspaces for state that survives between function invocations. Functions do not see the base workspace by default; scripts do. The distinction between a script and a function is the most consequential design decision in the file model and is the source of recurring surprises for new users.

Scripts and functions

A .m file is either a script or a function, distinguished by whether its first non-comment line is the function keyword:

% File: doStuff.m   (a script)
x = 1:10;
y = x.^2;
plot(x, y)
% File: square.m    (a function)
function y = square(x)
    y = x.^2;
end

A script executes in the base workspace: variables set in the script appear in the workspace afterwards. A function executes in its own private workspace: variables in the function are invisible to the caller and disappear when the function returns. The script form is intended for interactive exploration; the function form is intended for reusable computation. The recommendation in code that will be read or maintained is to write functions.

Since R2016b, a script may contain local functions after the principal script body:

% File: pipeline.m
data = readmatrix("data.csv");
result = process(data);
disp(result)

function r = process(d)
    r = sum(d, 'omitnan');
end

The local functions are callable from the script and from each other, but not from outside the file.

Function workspace

Every function call creates a fresh, isolated workspace. Variables defined in the function do not exist outside it; variables outside the function do not exist inside it. Arguments are passed by value (with copy-on-write, so the operational cost is paid only on modification — see the note below).

function out = stage(x)
    y = x + 1;            % y is local to stage
    out = y * 2;
end

stage(3);                 % returns 8; y does not exist in the caller

The workspace of the currently executing function can be inspected with the whos command; the debugger can dbstep into a function and dbcont out of it.

A consequence of strict workspace isolation is that MATLAB does not have closures over caller variables. The exception — and the principal mechanism for callbacks and higher-order programming — is the anonymous function, which captures the workspace variables visible at the point of its creation:

function f = makeAdder(n)
    f = @(x) x + n;       % captures n by value
end

g = makeAdder(5);
g(10)                     % 15

The anonymous function @(x) x + n is a snapshot of n at the moment of construction; reassigning n afterwards does not change g. This is the cleanest form of closure available in the language.

Copy-on-write

MATLAB function arguments are value semantics: assigning to a parameter does not affect the caller’s variable. The implementation, however, is copy-on-write: a parameter that is read but not written shares storage with the caller’s variable, so passing a large array as a function argument is cheap. Modifying the parameter — by indexing assignment, by reassignment of a part, or by an arithmetic operation that produces a new array — triggers the copy.

function modify(A)
    A(1, 1) = 0;          % copy is made here, in this call
end

X = zeros(10000);
modify(X);                % X is unchanged outside

The user-visible consequence: a function that “modifies an array” must return the array. A function that produces several outputs returns a comma-separated list of them.

Persistent variables

A persistent declaration inside a function creates a variable that survives between calls but is invisible outside the function. It is initialised to [] on first reference; subsequent calls see the value it was left at:

function n = counter()
    persistent count
    if isempty(count)
        count = 0;
    end
    count = count + 1;
    n = count;
end

counter()    % 1
counter()    % 2
counter()    % 3

A persistent variable is cleared by clear functions or by editing the function file; otherwise it persists for the lifetime of the MATLAB session. The mechanism is the canonical way to implement function-private state (a cache, a counter, a lazily initialised resource) without using a global variable.

Global variables

A global declaration declares a variable in a shared global workspace that is visible to any function that issues the same global declaration:

function set()
    global CONFIG
    CONFIG = struct('verbose', true);
end

function go()
    global CONFIG
    if CONFIG.verbose
        disp("verbose mode")
    end
end

Global variables are widely discouraged in modern MATLAB code: they break the isolation that makes functions easy to reason about, they hide dependencies, and they confound parallel execution. The recommended alternatives are explicit arguments, a handle class for shared state, or a configuration function that returns the relevant values.

The base workspace and evalin / assignin

The REPL maintains the base workspace. A function does not see it. Two functions — evalin('base', expr) and assignin('base', name, value) — provide controlled access from a function to the base workspace and to caller workspaces. They are used sparingly, chiefly by debugging tools, GUI callbacks, and tutorials; most production code does not touch them.

function inject(name, value)
    assignin('base', name, value);
end

inject('x', 42);          % afterwards, the base workspace has x = 42

The dual evalin('caller', ...) reads from the immediate caller’s workspace. The mechanism’s principal legitimate use is in implementing pseudo-language constructs (e.g., the tic/toc pair).

The path

MATLAB resolves an unqualified function name by walking the search path — an ordered list of directories — and choosing the first match. The path is configured with path, addpath, rmpath, and is initialised from pathdef.m at startup. The current working directory is always first; the standard library is at the end.

addpath("/Users/me/code/utils")
rmpath("/Users/me/code/utils")
which sin                  % shows the file backing sin: ".../toolbox/matlab/elfun/sin.m"

A function takes precedence over a variable of the same name only if there is no variable in scope. Once sum = 5 has been assigned, sum(x) raises an error because sum is a numeric variable and 5(x) is not meaningful. The remedy is clear sum.

Packages and namespaces

Functions and classes can be organised into packages by placing them in a folder whose name begins with +. A function +foo/bar.m is referred to as foo.bar:

% +stats/mean2.m
function m = mean2(x)
    m = sum(x(:)) / numel(x);
end

% caller
stats.mean2(rand(3,3))

Packages may nest: +foo/+bar/baz.m is foo.bar.baz. Packages provide namespacing that is otherwise absent from MATLAB’s flat function namespace; a class in a package is referred to with the package-qualified name. The mechanism is the modern replacement for the much older @-folder class form and is the recommended way to organise non-trivial codebases.

Class folders and methods

A folder named @MyClass containing @MyClass/MyClass.m defines a class; additional .m files in the folder are methods of the class. The mechanism predates the classdef keyword by a decade and remains in widespread use in legacy code; new code uses classdef files in a regular or package folder and reserves the @-folder form for code that must split a class across files.