ABAP traps: Lifetime of functional operand values
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 Shai Sinai 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( ).
It makes sense though. The data returned by the functional method only exists within the loop, so any field symbol assigned to it will not be assigned outside the loop.
I understood it once I saw how it behaves, but I don't think it's obvious that the data returned by the functional method should only exist within the loop.
Consider two other scenarios:
--> Scope is wider than expected
Or
So yes, it makes sense with an understanding of the subtleties of stacks and heaps and temporary data objects, but on a superficial level it can look confusing.
Well,
Just to clarify:
This is standard behavior of LOOP AT … ASSIGNING <fs>, regardless the functional method call nor the inline field-symbol declaration.
No, it's not the standard behaviour regardless of functional method calls. It behaves one way with a local itab and another with a functional operand. That's the interesting bit that prompted me to blog it, because it's easy to overlook if you're not aware of it.
Copy/paste the code into a test program and you'll see in the first example the assignment remains beyond the loop.
Surprisingly,
I could swear this is standard behavior of LOOP AT … <ASSIGNING> (Field symbol isn't assigned anymore after loop), but I double-checked it right now and it seems you are right.
Hence, the inconsistent behavior with functional method calls seems like a bug. You should open an incident regarding it.
I don't believe it's a bug, it's just one of the quirks of ABAP.
Theoretically you shouldn't be able to use field symbols in this way at all, because, according to SAP, assigning a temporary data object to a field symbol would not make sense. But in a LOOP statement it suddenly makes sense 🙂
The trick here is that it's not about the assignment, but how long the result of the functional method call result exists in memory, in this case it exists throughout the LOOP-ENDLOOP statement block and then disappears, the field symbol no longer has anything to point to. But if using a local variable, it still exists and the field symbol remains valid.
You got a point there!
I think it's 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. "
You are right! I somehow missed that.
But the main point was that it's not obvious and easy to make that mistake when refactoring. And it goes the opposite way to my second example to Matthew above: CLEAR/FREE is also meant to make stuff disappear, but it doesn't. Them's the quirks of ABAP.
Interesting. That's what I assumed too.
Actually I'd think that the bug is that reference still exists afterwards. "What happens in the loop, stays in the loop". 🙂
I expected the reference pointing to a variable to continue to exist, it's same way as when you do a LOOP ... INTO data(x) - the variable x continues to hold the last value we put in it until the procedure is exited.
I think it makes sense. Since the field symbol pointed to the mathematic result, after the calculation done ( end of loop),ABAP return from get_strings( ) method to the main program it mean the method itself not exist in the main program anymore then the field symbol assigment also end. In other case you already assigned the result to a helper variable then field symbol pointed to that helper variable then ofcouse it would be exist after the loop.
Hi Mike, thanks for your post. Two remarks: 1) a little typo: loop at obj->method_that_returns_itab( ) into field-symbol(<val>) gives as syntax error, should the INTO be changed to ASSIGNING.
2) you could update your post (for those who don't read the comments) to include the essential post of Shai Sinai June 21, 2018 at 12:45 pm (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. ”)
Thanks Sandra, good points! I have updated the blog with both suggestions.
Sometimes, I'm astonished myself, what I've found out and written to the documentation ...
Believe me, I'm just hacking around and trying things like you do and hope that you read it before trying yourself. But alas, it's just too much stuff to bring it to any point.