Here’s the thing, the rendezvous is synchronization-for-data-transfer.
What your mental model is saying is that the rendezvous (accept
…end
) is the [sub]task… the mismatch here will cause troubles for you. — I would recommend using a task to implement some protocol a few times to disabuse that notion and train yourself on how they actually work.
Example, given a process where you need a client doing (A
, then B
[same client]) or C
, repeatedly would be done with:
Task Example is
Entry A;
Entry B;
Entry C;
Entry Done;
End Example;
Task Body Example is
Finished : Boolean := False;
Begin
Loop
Exit when Finished;
Select
Accept A;
Accept B;
or
Accept C;
or
Accept Done;
Finished:= True;
End Select;
End Loop;
End Example;
The above implements the above-stated protocol — with the addition of Done
for signaling shutdown. (Note, the reason that A
then B
works here is because of the unstated assumption that client code will call entry-A, then entry-B, not ‘randomly’ spamming A or B.)
Now, continuing on (after you’ve implemented a few sequence-protocols, let’s add in data-transfer:
Task Example_2 is
Entry Put( Value : in Natural );
Entry Get( Value : out access Natural );
End Example_2;
Task Body Example_2 is
Has_Value : Boolean := False;
The_Value : Natural;
Begin
Loop
Select
accept Put( Value : in Natural ) do
Has_Value:= True;
The_Value:= Value;
end Put;
or
accept Get( Value : out access Natural ) do
Value:= (if Has_Value then
New Natural'( The_Value )
else Null);
Has_Value:= False;
end Get;
End Select;
End Loop;
End Example_2;
And, if you’re going to be using unconstrained types, like String
, you’re going to have to use a by-parts approach:
Task Example_3 is
Entry Put( Value : in String );
Entry Get( Value : out Natural );
Entry Get( Value : out String );
End Example_3;
Task Body Example_3 is
Package Word_List is new Ada.Containers.Indefinite_Vectors
( Element_Type => String, Index_Type => Positive );
Items : Word_List.Vector;
Index : Natural:= Natural'First;
Begin
Loop
Select
accept Get( Value : out Natural ) do
Value:= (if Index not in 1..Items.Length then 0 else Items(Index)'Length);
end Get;
accept Get( Value : out String ) do
Value:= (if Index not in Positive then "" else Items(Index));
end Get;
or
accept Put( Value : in String ) do
Items.Append( Value );
Index:=
end Put;
End Select;
End Loop;
End Example_3;
Function Example_Usage return String is
Length : Natural;
Begin
-- In order to retrieve the value, we need two rendezvous:
-- First, to retrieve the length; and after generating a string of that length,
-- Second, to populate that string with that data.
Example_3.Get( Length );
Return Result : String(1..Length) do
Example_3.Get( Result );
End return;
End Example_Usage;
The reason that you need to split things this way is because, with an out
parameter (or in out
) you cannot [re-]set the length of said parameter; to work around this, you get the length you need, then pass in the appropriately-sized array. — For explanations on why you have to do this, see my posts on the threads on strings/string-initialization: here and here, but the TL;DR of it is this: given a constrained value, you cannot re-set the constraints thereon; you may return results of an unconstrained subtype because the value thereof provides the constraints thereon.