Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
pokrakam
Active Contributor
A nasty bug-hunt led me to the discovery of an unexpected behaviour of ABAP, which I thought I'd share:

Usually ABAP's data elements' scope and lifetime is across an entire procedure. If you code an inline declaration such as data(x) inside a loop, x will continue to exist outside the loop.

Even helper variables within functional expressions are procedure-global, as I found out in How local is a 'local auxiliary field'?

ABAP doesn't do stuff local to a loop... or so I thought!

The scenario is simple: We have a loop, and at the end of the loop we want to re-use the last value:
loop at itab into val. 
...
endloop.
write: / |The last value was: { val }|.

This also works if the source is a functional operand (e.g. functional method):
loop at obj->method_that_returns_itab( ) into val. 
...
endloop.
write: / |The last value was: { val }|.

But sometimes we may use field symbols, and this is where the fun starts:
loop at  obj->method_that_returns_itab( ) assigning field-symbol(<val>). 
write <val>. "<--- OK!
endloop.
write: / |The last value was: { <val> }|. "<--- Dump! Field symbol not assigned

Here a functional operand is used effectively as a sort of anonymous data element: the method call is completed when the loop starts up, and the field symbol continues to point to the value and is moved down the table elements as the loop progresses.

Yet after the loop is exited, the value is no longer accessible via field symbol or reference. I did not expect this loop-local lifetime. Another unexpected quirk of ABAP  🙂

Update: 

As shais helpfully pointed out in the comments: this is even mentioned in the documentation:

“If the internal table is specified as the return value or result of a functional method, a constructor expression, or a table expression, the value is persisted for the duration of the loop. Afterwards, it is no longer possible to access the internal table. ”

Documented, but still unexpected.

Full test program below.
REPORT z_functional_operand_lifetime.

CLASS lcl_test DEFINITION CREATE PUBLIC.
PUBLIC SECTION.
METHODS start.
PRIVATE SECTION.
METHODS get_strings RETURNING VALUE(result) TYPE stringtab.
ENDCLASS.

CLASS lcl_test IMPLEMENTATION.

METHOD get_strings.
result = VALUE #( ( `A` ) ( `B` ) ( `C` ) ).
ENDMETHOD.

METHOD start.
DATA: s TYPE string,
sref TYPE REF TO string.
FIELD-SYMBOLS <s> TYPE string.

WRITE: / 'LOOP AT strings ASSIGNING <s>.'.
DATA(strings) = get_strings( ).
LOOP AT strings ASSIGNING <s>.
WRITE: / <s>.
ENDLOOP.
WRITE: / COND #( WHEN <s> IS ASSIGNED
THEN |Assigned: { <s> }|
ELSE 'Not assigned' ).
ULINE.

WRITE: / 'LOOP AT get_strings( ) INTO s.'.
LOOP AT get_strings( ) INTO s.
WRITE: / s.
ENDLOOP.
WRITE: / COND #( WHEN <s> IS NOT INITIAL
THEN |Value: { s }|
ELSE 'Not assigned' ).
ULINE.

WRITE: / 'LOOP AT get_strings( ) ASSIGNING <s>.'.
LOOP AT get_strings( ) ASSIGNING <s>.
WRITE: / <s>.
ENDLOOP.
WRITE: / COND #( WHEN <s> IS ASSIGNED
THEN |Assigned: { <s> }|
ELSE 'Not assigned' ).
ULINE.

WRITE: / 'LOOP AT get_strings( ) REFERENCE INTO sref.'.
LOOP AT get_strings( ) REFERENCE INTO sref.
WRITE: / sref->*.
ENDLOOP.
WRITE: / COND #( WHEN sref IS BOUND
THEN |Bound: { sref->* }|
ELSE 'Not bound' ).
ENDMETHOD.

ENDCLASS.

START-OF-SELECTION.
NEW lcl_test( )->start( ).
14 Comments