Skip to content
VLSI Mentor

Tcl Part I · Module 10 · Procedures and Reuse

return, and the Value a Procedure Produces

A procedure always produces a value, whether or not you asked it to. When you write return, that is the value; when you do not, Tcl hands back whatever the last command in the body happened to produce — and that is the empty string far more often than people expect, because puts produces nothing and an if whose branch was not taken produces nothing either. This chapter settles the rule, shows the three shapes return takes, and argues for a habit that removes the whole class of bug where a procedure silently returns nothing and the caller carries on with it.

Foundation10 min readTclPart IProceduresreturnValues

Part I · Module 10 · Chapter 10.5 · return, and the Value a Procedure Produces

1. Returning explicitly

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc classify {n} {
    if {$n < 0} {
        return "negative"
    }
    return "non-negative"
}

puts [classify -5]
puts [classify 5]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
negative
non-negative

return ends the procedure immediately and supplies the result. Everything after it in the body is not executed.

With no argument, it returns the empty string:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc quiet {} {
    return
}
puts "'[quiet]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
''

2. The implicit result

Without a return, a procedure gives back the result of its last command:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc implicit {} {
    set x 5
    expr {$x * 2}
}
puts [implicit]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
10

That is convenient for short procedures and it has a sharp edge, because many commands produce nothing. Chapter 10.1 §4 showed the cases; here they are together:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc endsSet   {} { set y 7 }
proc endsPuts  {} { puts -nonewline "" }
proc endsEmpty {} { }
proc endsIf    {n} { if {$n > 0} { return "positive" } }

puts "set   : '[endsSet]'"
puts "puts  : '[endsPuts]'"
puts "empty : '[endsEmpty]'"
puts "if yes: '[endsIf 5]'"
puts "if no : '[endsIf -5]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set   : '7'
puts  : ''
empty : ''
if yes: 'positive'
if no : ''

Only the first and fourth lines produced a value. set returns what it assigned, so endsSet gives 7. The other three give the empty string: puts returns nothing, an empty body has no last command, and an if whose branch was not taken produces nothing either.

3. Returning early

return works from anywhere in the body, including inside loops and switch:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc find_port {ports want} {
    foreach p $ports {
        if {$p eq $want} {
            return "found $p"
        }
    }
    return "not found"
}

puts [find_port {clk rst data} data]
puts [find_port {clk rst} data]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
found data
not found

It leaves the procedure, not just the loop — the distinction Chapter 9.10 §4 relied on to escape nested loops in one step. The same applies inside a switch:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc runs_for {mode} {
    switch -- $mode {
        fast { return 1 }
        full { return 10 }
    }
    return 0
}

foreach m {fast full other} {
    puts "$m -> [runs_for $m]"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
fast -> 1
full -> 10
other -> 0

That final return 0 is doing real work: without it, an unmatched switch would fall through to the end of the body and the procedure would return the empty string rather than 0.

4. Return on every path

The habit that removes the whole problem: if a procedure is supposed to produce a value, make every path end in an explicit return.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# fragile — the third case returns "" silently
proc grade_bad {n} {
    if {$n >= 90} { return A }
    if {$n >= 80} { return B }
}

# solid — every path is explicit
proc grade {n} {
    if {$n >= 90} { return A }
    if {$n >= 80} { return B }
    return F
}

puts "bad : '[grade_bad 95]' '[grade_bad 50]'"
puts "good: '[grade 95]' '[grade 50]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
bad : 'A' ''
good: 'A' 'F'

5. Returning an error instead of a value

Sometimes the honest answer is that there is no value. error raises, and the caller deals with it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc must_be_positive {n} {
    if {$n <= 0} {
        error "expected a positive number, got '$n'"
    }
    return $n
}

puts [must_be_positive 5]
if {[catch {must_be_positive -1} msg]} {
    puts "rejected: $msg"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
5
rejected: expected a positive number, got '-1'

return -code error does the same thing and lets you set the message in the same command:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc also_fails {n} {
    if {$n <= 0} {
        return -code error "not positive: '$n'"
    }
    return $n
}

if {[catch {also_fails 0} msg]} {
    puts "rejected: $msg"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
rejected: not positive: '0'

For ordinary validation, error reads better and is what you will see most. return -code matters when you are writing something that has to control exactly how it finishes — Module 14 covers that properly.

6. A worked example

A parser that returns a value, nothing, or an error, deliberately:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc parse_setting {line} {
    set t [string trim $line]

    # a blank or comment line is legitimately nothing
    if {$t eq ""}               { return "" }
    if {[string match "#*" $t]} { return "" }

    # anything else must be a setting
    if {![string match "*=*" $t]} {
        error "not a setting: '$t'"
    }

    set i [string first "=" $t]
    set key   [string trim [string range $t 0 [expr {$i - 1}]]]
    set value [string trim [string range $t [expr {$i + 1}] end]]
    return [list $key $value]
}

foreach line {"width = 32" "# a note" "" "nonsense"} {
    if {[catch {parse_setting $line} r]} {
        puts "  ERROR  $r"
    } elseif {$r eq ""} {
        puts "  skip   '$line'"
    } else {
        puts "  ok     [lindex $r 0] -> [lindex $r 1]"
    }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  ok     width -> 32
  skip   '# a note'
  skip   ''
  ERROR  not a setting: 'nonsense'

Three outcomes, three mechanisms, and the caller can tell them apart — which it could not if every failure returned an empty string.

7. Exercises

Exercise 1. What does a procedure return if it has no return and its last command is puts "done"?

Exercise 2. What does return with no argument give back?

Exercise 3. What is wrong with this, and when does it show up?

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc sign {n} {
    if {$n > 0} { return "+" }
    if {$n < 0} { return "-" }
}

Exercise 4. Write first_even returning the first even number in a list, or the empty string if there is none.

Exercise 5. When should a procedure raise an error rather than return an empty string?

Exercise 6. Why does the final return 0 matter in the runs_for example in section 3?

Answers

1. The empty string. puts prints its argument and returns nothing, so the procedure's implicit result is nothing.

2. The empty string.

3. Zero takes neither branch, so sign 0 falls off the end and returns the empty string instead of something meaningful. It shows up the first time the input is exactly zero — which, for a function about signs, is both the most interesting case and the one least likely to be in the first test. Add return "0" or similar as a final explicit path.

4.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc first_even {values} {
    foreach v $values {
        if {$v % 2 == 0} { return $v }
    }
    return ""
}
puts "'[first_even {1 3 4 5}]'"
puts "'[first_even {1 3 5}]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'4'
''

5. When carrying on would be wrong. An empty string means "the answer is nothing" and the caller can reasonably proceed; an error means "there is no answer" and the caller must not proceed as though there were. Returning empty for a genuine failure converts a loud problem into a silent one.

6. Because a switch that matches nothing produces the empty string, and without the final return 0 that empty string would become the procedure's result. The call runs_for other would yield '' instead of 0, and arithmetic on it would then fail somewhere else.

8. Remember

  • return value ends the procedure immediately and supplies the result; return alone gives the empty string.
  • Without a return, the result is the last command's result — which is set's assigned value, or nothing at all from puts, an empty body, or an untaken if.
  • An untaken if at the end of a body is the commonest cause of a procedure silently returning nothing.
  • return leaves the procedure, not just an enclosing loop or switch — which is how you escape nested loops in one step.
  • Give every path an explicit return once the body has branches. The implicit result is fine only for a single-expression procedure.
  • A switch with no match produces the empty string, so a procedure ending in one needs a fallback return.
  • error and return -code error raise instead of returning. Use them when carrying on would be wrong.
  • Empty string and error are different contracts: "the answer is nothing" versus "there is no answer".

Where this fits

Part of the Tcl curriculum.