MATLAB Function Argument Validation

SkillDev tools

Use when writing MATLAB functions with arguments blocks — repeating arguments (arguments (Repeating)), .?ClassName property import in constructors, name-value forwarding with namedargs2cell, or migrating from inputParser or validateattributes. Also when reviewing signatures for implicit-conversion pitfalls (size reshaping, class coercion, computed defaults), or when restricting, constraining, or validating function inputs — scalar vs vector enforcement, type rejection, size checking. Also when asked to harden inputs, make a function more robust, tighten input checking, or rewrite for safer input acceptance — even without "validation" or "arguments block" wording. Triggers on: arguments block, mustBe validators, name-value arguments, varargin, inputParser, nargin/narginchk, restrict input, type checking, scalar constraint, harden inputs, defensive programming. Not for: App Designer callbacks, Simulink mask parameters, class inheritance, or runtime validation outside function or property declarations.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the MATLAB Function Argument Validation skill

What this skill tells your AI

The instructions your AI receives, as published by matlab/matlab-agentic-toolkit in skills-catalog/matlab-programming/matlab-validate-function-arguments/SKILL.md and read by ahel’s review.

Write robust MATLAB functions using arguments blocks with correct semantics for size, class, repeating arguments, and property import.

When to Use

  • Writing a function with an arguments block
  • Using repeating arguments (arguments (Repeating))
  • Writing a class constructor that accepts name-value arguments
  • Migrating from inputParser or validateattributes
  • Reviewing a function signature for implicit conversion pitfalls
  • Choosing between class specs, mustBeA, and validators

When NOT to Use

  • Basic MATLAB programming without argument validation
  • App building or UI components (use matlab-building-apps)
  • Unit testing (use matlab-write-tests)
  • General OOP class design unrelated to argument validation
  • Simple if/error guard clauses for runtime invariants inside a function body (those aren't input validation — leave them as-is unless the user asks for an arguments block specifically)
  • .mlx (Live Script) or .mlapp (App Designer) files — these are binary ZIP containers. Don't unzip them or attempt structural edits. If the user asks to add an arguments block to a function inside one, ask them to export to plain .m first, or open the file in MATLAB and edit it there.

Critical Misconceptions

These are things the agent commonly gets wrong. Read these FIRST.

Size specs RESHAPE, not reject

WRONG belief: (1,:) rejects column vectors with an error.

ACTUAL behavior: MATLAB silently reshapes the input to fit the declared size.

function out = myFunc(x)
    arguments
        x (1,:) double
    end
    out = x;
end

myFunc([1; 2; 3])  % Does NOT error! Returns [1 2 3] (reshaped to row)

A column vector [1;2;3] passed to (1,:) becomes a row vector [1 2 3]. To actually reject column vectors, use a validator:

function out = myFunc(x)
    arguments
        x {mustBeNumeric, mustBeRow}
    end
    out = x;
end

Class specs CONVERT, not reject

WRONG belief: double in the arguments block rejects non-double inputs.

ACTUAL behavior: MATLAB attempts implicit conversion to the declared class.

function out = myFunc(x)
    arguments
        x double
    end
    out = x;
end

myFunc('hello')  % Does NOT error! Returns [104 101 108 108 111] (ASCII codes)
myFunc(single(3.14))  % Does NOT error! Returns double(3.14)

To reject without converting, use mustBeA or mustBeFloat:

x {mustBeA(x, "double")}           % Rejects single, char, int32, etc.
x {mustBeFloat}                     % Accepts single OR double, rejects char/int
x {mustBeNumeric}                   % Accepts any numeric, rejects char/string

Numeric validators don't reject complex values

WRONG belief: mustBeNumeric, mustBeFinite, and mustBeInteger reject complex inputs like 1+2i.

ACTUAL behavior: Complex values are numeric, finite, and (when their real and imaginary parts are integer-valued) integer — so 1+2i passes all three silently. Sizes, indices, counts, and most physical scalars should reject complex values explicitly:

function H = hilb2(n)
    arguments
        n (1,1) {mustBeInteger, mustBePositive, mustBeReal}
    end
    H = 1./((1:n)' + (0:n-1));
end

Add mustBeReal whenever a complex input would be nonsensical for the function's contract.

Computed defaults see PASSED values, not declared defaults

WRONG belief: Default expressions use the declared default values of earlier arguments.

ACTUAL behavior: Default expressions evaluate using the actual passed values.

function out = myFunc(fs, windowSize)
    arguments
        fs (1,1) double {mustBePositive}
        windowSize (1,1) double {mustBePositive} = round(fs / 10)
    end
    out = windowSize;
end

myFunc(8000)      % windowSize = round(8000/10) = 800 (uses passed fs)
myFunc(8000, 256) % windowSize = 256 (explicitly passed)

This eliminates the need for sentinel values ([]) and post-validation fixup.

~ is a valid placeholder — don't rename it

WRONG belief: A name in the arguments block is mandatory, so a ~ placeholder in the function signature must be renamed to obj/this/etc.

ACTUAL behavior: ~ is valid as an argument-block name (R2019b+). Keep it.

function normalize(~, results)
    arguments
        ~
        results (1,:) struct
    end
    ...
end

When a method signature uses ~ to mark an unused input (e.g. an unused class instance), preserve the ~ in the arguments block:

  • No class or size spec on ~ — type specs like (1,1) MetricCalculator require a name, so adding one forces a rename. Skip the spec entirely.
  • No %#ok<INUSL> / %#ok<INUSA> / %#ok<MANU> pragma~ already tells the lint pass the input is intentionally unused.
  • Don't rename ~ to obj to "tighten validation." Renaming reintroduces the lint warning the original ~ was suppressing, with no contract benefit.

Patterns

Repeating Arguments

Use arguments (Repeating) for functions accepting variable groups of arguments (like plot(x1,y1,x2,y2,...)). Each declared variable becomes a cell array in the function body.

function plotMultiSeries(x, y)
    arguments (Repeating)
        x (1,:) double {mustBeFinite}
        y (1,:) double {mustBeFinite}
    end

    figure;
    hold on;
    for i = 1:numel(x)
        plot(x{i}, y{i});
    end
    hold off;
end

Key rules:

  • Variables become cell arrays — access with x{i}, not x(i)
  • All variables in the group repeat together (complete sets)
  • Repeating arguments cannot have default values
  • Zero repetitions is valid (produces empty cell arrays)

Output Argument Validation — arguments (Output) and (Output, Repeating)

R2022b+. Output argument validation is unavailable in earlier releases — input validation (the bulk of this skill) works from R2019b onward.

Use arguments (Output, Repeating) to declare a function whose output count scales with nargout. The block must declare exactly one name — MATLAB rejects multi-name repeating output blocks with MATLAB:functionValidation:MultipleRepeatingOutputs. To return groups of related values (e.g. triples), pack them into the single output and have the caller request N * groupSize outputs.

function out = productTriples()
    arguments (Output, Repeating)
        out (1,1) double
    end
    out = cell(1, nargout);
    for k = 1:3:nargout
        b = rand;
        c = rand;
        out{k}   = b * c;
        out{k+1} = b;
        out{k+2} = c;
    end
end

Caller: [p1, a1, b1, p2, a2, b2] = productTriples();

Key rules:

  • One name onlyarguments (Output, Repeating) a; b; c; end errors at parse
  • The output is assigned a 1×nargout cell array; comma-list expansion at the call site distributes its contents across the requested LHS variables
  • Validators on the single declared name apply to each cell element
  • nargout tells the body how many outputs were requested; zero is valid

Composition Rules — What Can Coexist

A function may have multiple arguments blocks. The rules below apply to the function as a whole — argument categories must appear in this order across all blocks combined:

required → optional (with defaults) → repeating → name-value

Violating the order at any point — even across separate blocks — fails to parse. Don't trust intuition: all of these compose legally in one function:

  • required + optional + repeating + name-value (.?ClassName allowed too)
  • required + repeating + name-value
  • repeating + name-value
  • (Output, Repeating) plus any combination of input blocks
  • ~ placeholders inside a (Repeating) block

Multiplicity:

Block kindHow many allowed
Plain arguments (positional and/or name-value)Multiple, in order
arguments (Repeating) (input)Exactly one per function
arguments (Output, Repeating)Exactly one per function
Name-value structs across plain blocksMultiple — different struct names

Parse-time error identifiers — when in doubt, write the function and let MATLAB tell you. These are the only errors composition can produce; the names spell out the rule:

IdentifierMeans
MATLAB:functionValidation:RequiredAfterOptionalA required arg appears after one with a default
MATLAB:functionValidation:OptionalAfterRepeatingAn optional/positional arg appears after the repeating block
MATLAB:functionValidation:PositionalAfterNamedA positional or repeating arg appears after a name-value arg
MATLAB:functionValidation:MultipleRepeatingBlocksMore than one (Repeating) block (input or output)
MATLAB:functionValidation:RepeatingHasDefaultA (Repeating) arg has a default value

If you're unsure whether a combination is legal, don't fabricate a restriction — write a minimal version and run it. The error identifier (or its absence) is the answer.

Maximal example

A single function with required + optional + ignored + repeating + ignored repeating + name-value + class-imported name-value (here SensorConfig — the class defined in the .?ClassName section above):

function maximal(req, opt, ~, x, ~, options, classArg)
    arguments
        req (1,1) double
        opt (1,1) double = 7
        ~
    end
    arguments (Repeating)
        x (1,1) double
        ~
    end
    arguments
        options.Title (1,1) string = "default"
        options.Verbose (1,1) logical = false
        classArg.?SensorConfig
    end
    % function body
end

Call: maximal(1, 2, 3, 4, 'a', 5, 'b', Title="t", SampleRate=44100) — parses and runs. SampleRate is one of the name-value args imported from SensorConfig by the .? line.

Don't pick a class with no public-settable properties for .?ClassNameonCleanup, for example, has only task (private set), so .?onCleanup imports zero name-value args. The line is syntactically valid but a no-op, and any "name-value" the caller writes against it is treated as an unknown name — see "unknown Name=Value is silently swallowed" in Common Mistakes.

For combining repeating args with name-value options, see the Maximal example above and references/examples/repeating-args.md.

.?ClassName — Import Properties as Name-Value Args

Use .?ClassName in constructors to derive name-value arguments directly from property definitions. This avoids redeclaring properties in the arguments block.

For additional patterns — overriding specific properties, static factory with forwarding, and wrapping graphics-class properties — see references/examples/dot-question-syntax.md.

classdef SensorConfig
    properties
        SampleRate (1,1) double {mustBePositive} = 1000
        Resolution (1,1) double {mustBePositive, mustBeInteger} = 16
        FilterOrder (1,1) double {mustBePositive, mustBeInteger} = 4
        Label (1,1) string = "unnamed"
    end

    methods
        function obj = SensorConfig(nvArgs)
            arguments
                nvArgs.?SensorConfig
            end
            props = fieldnames(nvArgs);
            for i = 1:numel(props)
                obj.(props{i}) = nvArgs.(props{i});
            end
        end
    end
end

Benefits:

  • No duplication of size, class, validators, or defaults
  • Constructor stays in sync when properties are added or removed
  • Tab completion shows all settable properties automatically

namedargs2cell — Forward Name-Value Args

Use namedargs2cell to convert a validated struct back to a name-value cell array for forwarding to other functions:

function obj = fromPreset(presetName, nvArgs)
    arguments
        presetName (1,1) string
        nvArgs.?SensorConfig
    end

    switch presetName
        case "audio"
            defaults = struct(SampleRate=44100, Resolution=24, Label="audio");
        case "vibration"
            defaults = struct(SampleRate=10000, Resolution=16, Label="vibration");
    end

    % Apply overrides
    overrides = fieldnames(nvArgs);
    for i = 1:numel(overrides)
        defaults.(overrides{i}) = nvArgs.(overrides{i});
    end

    args = namedargs2cell(defaults);
    obj = SensorConfig(args{:});
end

Migration Decision Framework

When migrating from inputParser or validateattributes, use this decision tree for class handling:

Original checkWhat it doesArguments block equivalentWhy
@isnumericAccepts any numeric, no conversion{mustBeNumeric}No class spec — avoids conversion
@ischarAccepts char only{mustBeA(label, 'char')} or (1,:) charmustBeText/mustBeTextScalar widens to accept string
@islogicalAccepts logical only(1,1) {mustBeA(x, 'logical')}A logical class spec CONVERTS — logical(1)true, so 1/0 (and other convertible scalars) would be accepted where @islogical rejects them. Use mustBeA (no class spec) to reject without converting.
{'numeric'}Accepts any numeric, no conversion{mustBeNumeric}validateattributes meta-class — same set as @isnumeric
{'float'}Accepts single or double{mustBeFloat}Equivalent to {'single','double'}
{'integer'} (class spec, rare)Accepts only integer types (int8…uint64)Usually {mustBeNumeric, mustBeInteger}. Strict-only-if-intentional: {mustBeA(x, ["int8","int16","int32","int64","uint8","uint16","uint32","uint64"])}The class-only form rejects integer-valued doubles like 5.0 — usually accidental. Widen to the lenient pairing unless the original code clearly meant to reject doubles.
{'numeric'}, {'integer'} (class+attr)Numeric, integer-valued{mustBeNumeric, mustBeInteger}Common pairing — accepts both int types and integer-valued floats
{'single','double'}Restricts to float types{mustBeFloat}Class spec double would convert single→double
{'double'} exactRejects single, int, char{mustBeA(x, "double")}Class spec double would convert, not reject
@(x) isa(x,'MyClass')Class membership{mustBeA(x, "MyClass")}Validates without conversion

Key principle: If the original code REJECTS mismatched types, use validators (mustBeA, mustBeFloat, mustBeNumeric). If you WANT automatic conversion for caller convenience, use a class spec.

Anonymous-Function Validators

Don't drop these on migration — each has a built-in equivalent:

inputParser validatorArguments block equivalent
@(x) ~isempty(x){mustBeNonempty}
@(x) x > 0{mustBePositive}
@(x) x >= 0{mustBeNonnegative}
@(x) x < 0{mustBeNegative}
@(x) x <= 0{mustBeNonpositive}
@(x) isfinite(x){mustBeFinite}
@(x) isreal(x){mustBeReal}
@(x) isnumeric(x) && isreal(x){mustBeNumeric, mustBeReal}
@(x) ismember(x, set){mustBeMember(x, set)}
validatestring(x, set)Don't replace with mustBeMember. validatestring does case-insensitive prefix matching and returns the canonical form; mustBeMember is exact-match only. Keep the call inside the body and use the arguments block only for {mustBeTextScalar}.

For detailed migration examples, see references/migration-guide.md.

Conventions

  • Prefer string over char for new code (not migrations)
  • Never use varargin with manual parsing when arguments (Repeating) or name-value blocks can express the same interface
  • Use .?ClassName in constructors rather than redeclaring properties
  • Prefer computed defaults (= expression) over sentinel values with isempty checks, when the default depends only on earlier arguments
  • Don't combine mustBeNumeric with a double class spec to reject non-numeric input — the class spec converts char ('A'65) before the validator runs, defeating the check. Drop the class spec: x {mustBeNumeric} not x (1,1) double {mustBeNumeric}
  • Don't combine text validators with a string class spec — the class spec converts non-text (e.g., 42"42") before the validator runs, defeating the check. Use text validators alone: p {mustBeTextScalar} not p (1,1) string {mustBeTextScalar}
  • Never reach for varargin + manual class() checks to reject types. When a class spec like double would convert (rather than reject) char/single/int input, the right answer is a validator (mustBeNumeric, mustBeFloat, mustBeA), not a fallback to varargin. Falling back to varargin defeats the entire point of the arguments block.

Common Mistakes

MistakeWhy It's WrongCorrect Approach
Using varargin for repeated arg groupsRequires 30+ lines of manual parsingarguments (Repeating) — 3 lines
Redeclaring properties in constructorDuplication drifts out of syncnvArgs.?ClassName
Believing (1,:) rejects columnsIt silently reshapes themUse mustBeRow to reject
Believing double rejects charIt silently converts to ASCII before validators runDrop the double class spec and use {mustBeNumeric} alone, or {mustBeA(x,"double")} for strict class check
Using [] sentinel for computed defaultsOver-engineered, needs custom validator= expression referencing earlier args
Replacing @ischar with mustBeTextScalarWidens contract to accept stringsUse mustBeA(x,'char') for strict fidelity
Using (1,1) string {mustBeTextScalar}Class spec converts 42"42" before validatorUse {mustBeTextScalar} alone (no class spec)
Replacing validatestring with mustBeMembervalidatestring does case-insensitive prefix matching ('AU''auto') and returns the canonical form; mustBeMember is exact-match onlyKeep validatestring inside the body. Use the arguments block only for the type/size guard: mode {mustBeTextScalar} then mode = validatestring(mode, {...})
Omitting mustBeReal for numeric argsComplex values pass mustBeNumeric, mustBeFinite, and mustBeInteger silently — 1+2i is a finite numeric integerAdd mustBeReal whenever a complex input would be nonsensical (indices, sizes, counts, physical scalars)
Renaming ~ to obj/this in arguments block~ is a valid placeholder name in arguments blocks — renaming reintroduces the lint warning the original ~ suppressedKeep ~, no class/size spec, no %#ok pragma
Declaring multiple names in arguments (Output, Repeating)MATLAB errors with MultipleRepeatingOutputs — only one name is allowedDeclare a single output name; pack groups of values into that one cell array and have the caller request N * groupSize outputs
Claiming optional positional args and (Repeating) are mutually exclusiveThey aren't — required + optional + repeating + name-value all compose legally in orderSee "Composition Rules". When unsure, write the function and let MATLAB's parse-time error identifier (e.g. OptionalAfterRepeating, PositionalAfterNamed) tell you what's actually wrong
Assuming an unknown Name=Value errors loudly when the function has arguments (Repeating)It doesn't. With a (Repeating) block present, an unrecognized Name=Value is silently absorbed as two positional repeating args (name token, then value). One bad pair also reclassifies preceding valid name-value args back to positional, so set options revert to their defaults. (Without (Repeating), the same call errors with MATLAB:TooManyInputs.)A common way to land here: .?ClassName against a class with no public-settable properties (e.g. .?onCleanup) — the import exposes nothing, so every NV the caller writes against it is unknown. Pick a class with public-set properties. When debugging "why is my NV default showing up?", check the call for any unrecognized Name= — one bad name poisons the whole NV section

Validators Quick Reference

ValidatorPurposeNote
mustBeFloatAccept single or double onlyRejects int, char
mustBeA(x, classes)Strict class check, no conversionmustBeA(x, ["single","double"])
mustBeBetween(x, lo, hi, type)Range checkR2025a+. Pre-R2025a: use mustBeGreaterThan/mustBeLessThan
mustBeNonNanReject NaN valuesBuilt-in, not custom
mustBeMatrixRequire 2D (M-by-N)R2024b+
mustBeRowRequire 1-by-NR2024b+. Rejects columns unlike (1,:)
mustBeVectorAccept row or columnFlexible orientation
mustBeSortedElements in sorted orderR2026a+

For the complete validator reference, see references/validators-reference.md.


Copyright 2026 The MathWorks, Inc.


Signals

GitHub stars
1k
Forks
128
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
matlab-validate-function-arguments
Source
github.com/matlab/matlab-agentic-toolkit