8

I'm using Erlang R16B03.

This code:

list_dir(Directory, Retries) when is_integer(Retries), Retries > 0 ->
    Port = get_efile_port(),
    try erlang:port_info(Port) of
        Result ->
            error_logger:info_msg("list_dir - erlang:port_info(~p) ->  ~p ", [Port,Result])
    catch
        _:Reason ->
             error_logger:info_msg("list_dir - erlang:port_info(~p) -> {error, ~p }",[Port,Reason])
    end,
    case prim_file:list_dir(Port, Directory) of
        {error, einval} ->
            error_logger:info_msg(" list_dir -  port : ~p , directory : ~p", [Port, Directory]),
            clear_efile_port(),
            list_dir(Directory, Retries-1);
        Result ->
            Result
    end.

Generates the following compiler exception:

/basho/riak/deps/bitcask/src/bitcask_fileops.erl:855: variable 'Result' unsafe in 'try' (line 843)
ERROR: compile failed while processing /basho/riak/deps/bitcask: rebar_abort
make: *** [compile] Error 1

But if I rename the first use of the variable name Result to Res, it compiles fine, e.g:

list_dir(Directory, Retries) when is_integer(Retries), Retries > 0 ->
    Port = get_efile_port(),
    try erlang:port_info(Port) of
        Res ->
            error_logger:info_msg("list_dir - erlang:port_info(~p) ->  ~p ", [Port,Res])
    catch
        _:Reason ->
             error_logger:info_msg("list_dir - erlang:port_info(~p) -> {error, ~p }",[Port,Reason])
    end,
    case prim_file:list_dir(Port, Directory) of
        {error, einval} ->
            error_logger:info_msg(" list_dir -  port : ~p , directory : ~p", [Port, Directory]),
            clear_efile_port(),
            list_dir(Directory, Retries-1);
        Result ->
            Result
    end.

As far as I can see the variables are within two different scopes (try/catch and case).

Is this a compiler bug or have I failed to understand the Erlang syntax correctly?

bryan hunt
  • 644
  • 4
  • 20

3 Answers3

9

This is not a compiler bug. The problem is that in the first example, you use Result in two places: first in the try, and then again in the case. These are not two different scopes, but the same scope. The compiler is complaining because if there's no exception raised in the try, Result will be bound to the result of the erlang:port_info(Port) call, but if that call raises an exception, Result will not be bound. This means its use within the case will be ambiguous, since in the second case clause it would be matched if it's already bound, or bound to the result of prim_file:list_dir(Port, Directory) if it's not already bound.

Steve Vinoski
  • 19,847
  • 3
  • 31
  • 46
4

The try block itself returns a value. So you can avoid these situations like this

list_dir(Directory, Retries) when is_integer(Retries), Retries > 0 ->
    Port = get_efile_port(),
    Result = try erlang:port_info(Port) of
          ... 

etc.

For example, here's a couple of lines from a project I'm working on right now:

F = try
      python:call(P, 'python.repo_admin_cmd', 'repo_admin_cmd', [erlang:list_to_binary(Command) || Command <- Commands])
catch error:{python, _Class, _Argument, _StackTrace} ->
     error
end

If I were to try to assign "F" inside the "try" I'd get the same error you did!

2

Since you've matched to your Result clause, result is bound to value. And its scope is not restricted by block. Consider following code:

#! /usr/bin/env escript

main([What]) ->
    case What of
        Value when Value == "hello" ->
            Result = foo(Value),
            ok; 
        Value when Value == "goodbye" ->
            Result = foo(Value),
            ok  
    end,
    io:format("~p ~p",[Result, Value]).

foo("hello") ->
    "ohai";

foo("goodbye") ->
    "cya".

Even Value was bound only in case clause, it's still accessible in outer scope. Notice that same thing with Result variable which bounded in case clause too.

Viacheslav Kovalev
  • 1,745
  • 12
  • 17