http://www.perlmonks.org?node_id=189981

Maclir has asked for the wisdom of the Perl Monks concerning the following question:

Ok, it is late and my brain is fried. But I cannot see what I am doing wrong. I want call a subroutine, passing two variables as references. My understanding is that this is effectively a "call by name", and any modifications the subroutine makes to the variables within the subroutine scope will be reflected in the main program.

Here is a sample program:

#!/usr/bin/perl -w $template_file = "test.pl"; $template = "old value"; print "Before call: $template \n"; $cnt = read_template(\$template_file, \$template); print "$cnt lines read \n"; print "after call: $template \n"; sub read_template { my $file_name = ${shift()}; my $lines_in = ${shift()}; my $line_cnt = 0; if (!open (INPUT, "< $file_name")) { die "Can't open $file_name as input."; } else { while (<INPUT>) { $line_cnt++; $lines_in .= $_; } close (INPUT); } return($line_cnt); }

and here is the output:

Before call: old value 29 lines read after call: old value

What wrong assumptions have I made?

By the way, I am calling the subs this way as I am working on a CGI program that I want to make "mod_perl" safe

Replies are listed 'Best First'.
Re: Sub Params as references
by tstock (Curate) on Aug 14, 2002 at 03:33 UTC
    What wrong assumptions have I made?

    my $lines_in  = ${shift()};

    you made a copy of a variable instead of using the reference to the variable. your code will work if you do:
    my $lines_in = shift; [...] $$lines_in .= $_;
    Tiago
    Update: check out perlref if you want to know more about references.
Re: Sub Params as references
by kvale (Monsignor) on Aug 14, 2002 at 03:37 UTC
    The problem in your is with extracting the sub parms from  @_. With
    my $file_name = ${shift()}; my $lines_in = ${shift()};
    you are copying your dereferenced references into new local variables, which does not update the variables in the caller. Try instead
    sub read_template { my $file_name = shift; my $lines_in = shift; my $line_cnt = 0; if (!open (INPUT, "< $$file_name")) { die "Can't open $file_name as input."; } else { while (<INPUT>) { $line_cnt++; $$lines_in .= $_; } close (INPUT); } return($line_cnt); }
    This approach keeps the references and updates the caller's variables.

    -Mark

Re: Sub Params as references
by chromatic (Archbishop) on Aug 14, 2002 at 03:36 UTC

    You've copied the value pointed to by the reference. When you copy a reference, you're still pointing to the same value. When you copy a dereference, you get a fresh copy of the value.

    my $file_name = shift; my $lines_in = shift; # skip a few $$lines_in .= $_;
Re: Sub Params as references
by abaxaba (Hermit) on Aug 14, 2002 at 03:51 UTC
    First off -- There's not really a need to use references in this instance. I'll rarely ref a $string, unless it's a blessed $string, or if length($string) == $reallyLarge. I understand your motivation, saving on the return. Just something to keep in mind.

    That having been said, I suspect the issue is in your assignment (via ${shift()}). Since you wish to change the values of your references, I would make assignments through the reference itself. It appears that you're assigning the dereffed value of your stringrefs to scoped vars, not assigning local values to the referenence itself.

    Strings are really easy to deref -- just add another $. I think your subroutine should probably look like this: (Line Numbers added)

    1:sub read_template 2:{ 3: my($file_name_ref,$lines_in_ref) = @_; 4: my $line_cnt = 0; 5: open (INPUT, "$$file_name_ref") || 6: die "Can't open $$file_name_ref as input:$!"; 7: while (<INPUT>) 8: { 9: $line_cnt++; 10: $$lines_in_ref .= $_; 11: } 12: close (INPUT); 13: return($line_cnt); 14:}
    A couple things to note:
  • The local variables are assigned to the actual references (line 3)
  • What we actually open (line 5) is the dereffed $file_name($$file_name_ref). I've modified the open/die logic -- save yourself some keystrokes. I've also passed the $OS_ERROR ($!) to the die to aid in diagnostics (line 5,6)
  • In line 10 -- we assign through the refernce, to the referent. ($$lines_in_ref)
  • HTH!

    ÅßÅ×ÅßÅ
    "It is a very mixed blessing to be brought back from the dead." -- Kurt Vonnegut

      Your first comment - at least one of the strings can get rather large - hence the desire to avoid local copies and all the associated memory wastage. The use of $! in the die statement - the next task is to write a suitable error routine - since this will be a CGI program, and I want a generic fatal error logger to write out a nice HTML page, complete with necessary debugging details for the user to forward to the webmaster.

      Thanks again for pointing out my error - one should never try to read perlref late in the evening.

        ... I want a generic fatal error logger to write out a nice HTML page, complete with necessary debugging details for the user to forward to the webmaster.
        use CGI::Carp qw(fatalsToBrowser); would be a good starting point for development. This is also probably OK for an Intranet site, but if you put this live on the Internet, you may be exposing yourself too much, see Does fatalsToBrowser give too much information to a cracker?.
        Your first comment - at least one of the strings can get rather large - hence the desire to avoid local copies and all the associated memory wastage.

        Hmm, i could be completely wrong, (but i dont think so), but perl strings are never passed by value. Perl strings arent like base string types in most languages. They live inside of an SV, which contains a pointer to the block of memory holding the string along with a bunch of housekeeping information. And since only SV's are passed around on the stack your string isnt going to be copied. The usualy recommendations about passing hashes and lists by reference have to do with the way perl listifies these types when used as parameter arguments. Thus by passing a reference to the array you only pass one SV, but by passing a listified array you pass one SV for every element.

        Also, again I could be wrong about this, but I think perl variables are always passed by reference (actually not reference in the normal sense but a special kind of reference called an alias). This means that by modifying the contents of @_ directly you actually end up modifying the original value. This suggest to me quite strongly that perl variables are always passed "by reference".

        Yves / DeMerphq
        ---
        Software Engineering is Programming when you can't. -- E. W. Dijkstra (RIP)

Re: Sub Params as references
by Maclir (Curate) on Aug 14, 2002 at 03:41 UTC
    Doh!! Thanks all. I was modifying some older code that just used globals, and I chose not to dereference the right way.