MOVE_ALLOC extended to non-allocatable derived types containing allocatable components

Not sure if this has already been suggested. I can see some merit in generalising this intrinsic to accept derived types containing allocatable components, thus providing broader support for the move semantics becoming popular in other languages. Assuming that both the source and destination have compatible types (recursively, but not via POINTER or ALLOCATABLE), then all allocatable components would be moved over. I understand that this is tricky with MOVE_ALLOC being a subroutine rather than a a special form like ALLOCATE.

Hi Tim, thank you for your suggestion, but I’m not sure that I understand it correctly. I’ve applied move_alloc to derived types with allocatable components without issues. Problems do arise if there are pointers linked to those variables since, should the actual move occur, they will not be updated.

Hi ricriv, thanks for the reply and sorry, my fault for being unclear.

The idea would be for MOVE_ALLOC (or perhaps another version MOVE_ALLOC_ALL?) to operate on a value of a derived type that is not itself allocatable but contains allocatable components. Corresponding allocatable components would be moved over.

As a use case, think of a derived type implementing a sparse array with allocatable components for the row indices, values etc. So we have:

TYPE sparse
REAL,ALLOCATABLE,DIMENSION(:):: values
INTEGER,ALLOCATABLE,DIMENSION(:):: indices
INTEGER:: nrows,ncols
END TYPE sparse

TYPE(sparse):: a,b

CALL MOVE_ALLOC_ALL(a,b)

This would move one sparse array into another (essentially doing CALL MOVE_ALLOC(a%values,b%values) ; CALL MOVE_ALLOC(a%indices,b%indices); b%nrows=a%nrows;b%ncols=a%ncols ), even when the sparse array value itself sits on the stack or is an unboxed array component. Yes, you can fairly easily create a specific routine to do this, but having an intrinsic version could, I think, be helpful and bring move semantics more in line with assignment.

1 Like

Hi @TimBellerby and welcome.

I think there’s a misunderstanding in what the MOVE_ALLOC intrinsic does. It doesn’t actually perform any allocation, it just moves a descriptor from one ALLOCATABLE variable to another —that’s why it’s PURE, despite being a subroutine, since when FROM is allocated and no coarray-related synchronization is involved, the likelihood of it failing is low.

With that said, your example (with some spaces added for readability) becomes:

TYPE sparse
    REAL, ALLOCATABLE, DIMENSION(:) :: values
    INTEGER, ALLOCATABLE, DIMENSION(:) :: indices
    INTEGER :: nrows, ncols
END TYPE sparse

TYPE(sparse), ALLOCATABLE :: a, b

ALLOCATE (a)
CALL MOVE_ALLOC_ALL(a, b)

Which is probably not very useful on its own, but proves the point.

The main idea behind introducing MOVE_ALLOC was to avoid one copy when reallocation is involved.

In those “other languages” you mention, things might be passed by value, and idiomatically the result tends to be an expensive expression, so avoiding one copy is desirable. But that’s already covered by Fortran’s INTENT and CONTIGUOUSattributes… so subroutines might be a better fit than functions for that purpose.

1 Like

Thank you for your reply jwmwalrus.

I can fully see your point that Fortran does contain quite a few mechanisms which serve to avoid having to use move semantics as much as say C++ or Rust and I can certainly see some issues with my suggestion. However, if something like this could be done, I do think it would provide a additional and relatively cheap mechanism to further manage ownership of heap-allocated subobjects.

The idea behind my suggestion is that MOVE_ALLOC is a mechanism to transfer ownership. I think this could be generalised. If you assign two values with allocatable components, then you get copying (and possible reallocation) of those allocatable components. I am suggesting an option to do something very similar, but with the allocatable components transferred instead - the implementation could simply deallocate the allocatable components in the destination object, shallow copy the derived type over from the source and then null-out the allocatable component pointers in the source object to prevent duplicated ownership.

Just to note. In the example, you have changed the line TYPE(sparse):: a,b to TYPE(sparse), ALLOCATABLE:: a,b. I really meant TYPE(sparse):: a,b - the lack of ALLOCATABLE here was not a typo. The idea is to move allocations of components between objects of a derived type when those objects themselves are not allocatable.

Sure, I know it was not a typo. My point being that the MOVE_ALLOC intrinsic needs that attribute, since, as I said, it just transfers a descriptor —i.e., it does something the programmer couldn’t do on its own.

What you describe is how assignment works for derived type instances —i.e., every component on the lhs is (re)allocated to match the rhs.

I assume that, instead of hijacking MOVE_ALLOC, what you really want is a new intrinsic that behaves like assignment but moves the allocation from rhs to lhs instead of triggering a copy… and might cause the rhs to be in an undefined state? (that might be problematic if rhs is an argument).

Yes, many thanks that is exactly what I was suggesting, although the RHS would be in a defined state after the operation (allocatable components now all deallocated) rather than an undefined one, and if the source is an argument it would have to be INTENT(INOUT). You are right, a new intrinsic would be much better than overloading MOVE_ALLOC.

I am still uncertain about what you are requesting. What you describe above is already exactly what move_alloc() does with allocatable arguments. So it seems to me like the solution is simply to add that attribute to your two variables a and b and your problem is solved.

May I ask what would be the purpose of this?

Something like

subroutine transfer_values(from, to)
    type(sparse), intent(inout) :: from
    type(sparse), intent(out) :: to
    to = from
    call reset(from)
contains
    subroutine reset(x)
        type(sparse), intent(out) :: x
    end subroutine
end subroutine

Already does the job, but requires a copy. I just fail to see the applicability.

Like, why wanting to rename a variable half-way through runtime for the sake of it?

Very reasonable questions, all. I fully agree that just applied to whole variables, then this would be pretty pointless. However, I do think that the are use cases if the new intrinsic was applied to components of a derived type, or even more so to array elements.

Here is an example - a classic expandable array, but of a type that includes allocatable components. The proposed intrinsic would avoid having to hand-code MOVE_ALLOC_ALL. Also with generics on the way, then an intrinsic MOVE_ALLOC_ALL would allow expand_array in the below example to simply be generically parameterised on the array element type - otherwise you would be looking at requiring a programmer to define MOVE_ALLOC_ALL each concrete instance.

! Type with allocatable components (in module somewhere)
TYPE foo
   INTEGER, DIMENSION(:), ALLOCATABLE:: arr1, arr2, arr3
   INTEGER:: i1, i2, i3
END TYPE foo

! Expand allocatable array to new_size
SUBROUTINE expand_array(a,new_size)
 TYPE(foo), DIMENSION(:), ALLOCATABLE, INTENT(INOUT):: a
 INTEGER,INTENT(IN):: new_size 
 TYPE(foo), DIMENSION(:), ALLOCATABLE:: temp
 CALL MOVE_ALLOC(a,temp) 
 ALLOCATE(a(new_size))
 CALL MOVE_ALLOC_ALL(temp,a(1:SIZE(temp)))
END SUBROUTINE expand_array

! Move values/allocations of all sub-components from source to dest, 
! Hand coded here, but would be replaced by the proposed intrinsic
SUBROUTINE MOVE_ALLOC_ALL(source,dest)
 TYPE(foo), DIMENSION(:), INTENT(INOUT):: source, dest
 INTEGER:: i
 DO i=1,SIZE(source)
   CALL MOVE_ALLOC(source(i)%arr1, dest(i)%arr1)
   CALL MOVE_ALLOC(source(i)%arr2, dest(i)%arr2)
   CALL MOVE_ALLOC(source(i)%arr3, dest(i)%arr3)
   dest(i)%i1=source(i)%i1
   dest(i)%i2=source(i)%i2
   dest(i)%I3=source(i)%i3
 ENDDO
END SUBROUTINE MOVE_ALLOC_ALL

Ok, now I think I see what you are after. You want shallow copies for some components, and actual/deep copies for others. In this situation, here is how I might try to do that. I would define two derived types, one for the shallow components and another for the deep components, and another derived type for the combination of the two.

type shallow
   integer, allocatable :: i, j(:), k(:,:)
   integer :: a, b(:), c(:,:)
end type shallow
type deep
   integer, allocatable :: i, j(:), k(:,:)
   integer :: a, b(:), c(:,:)
end type deep
type combined
   type(shallow), allocatable :: s
   type(deep), allocatable :: d
end type combined
type(combined) :: x, y

Now suppose y has been populated with information, in both of its components, and you want to copy that to x. You want shallow copies for the shallow components, and deep copies for the deep components. The copy operation would then look like this:

call move_alloc( from=y%s, to=x%s )
x%d = y%d

Afterwards, x is a copy of what y used to be, y%s becomes unallocated, and y%d is unchanged.

Okay, in the context of optimizing reallocation, the example makes sense. Things get messier if arr1, arr2 and arr3 are themselves instances of derived types —thus proving your point about the need for a recursive-move mechanism.

The problem is that it’s too “low-level”, and, in the context of interoperability with C and compatibility with coarrays, would probably require a lot of constraints (which is kind of bad for an intrinsic).

Since your first post starts wit it, what has actually been proposed in the past, is a REALLOCATE statement. A processor (i.e., a compiler) might be able to implement such statement without too much performance degradation, by following an approach akin to what you described.