Dependency injection

How do you do dependency injection in Ada without using access types?

Let’s say I have a Connection record with Create function where I want to pass an object that handles the actual connecting for which I may have several implementations. I decided to use a limited interface as base type.

Now, in C++ I would do that with a unique_ptr and move operation. But I don’t know how to do it with Ada.

Edit: given the layout of the object is unknown, I think one option is to store the access type, and so I would have to pass the connector object as an access type. Is there any other way ?

Usually there is no need to have different connection implementation. You need different implementations of the connection object that would handle the connection, e.g. implement the protocol, use a TLS etc.

The Simple Components (Simple components for Ada) uses a factory object that creates a new connection object when a connection is about to be accepted.

You need an access type because the connection object is indefinite. The connection server maintains the connection objects once created. E.g. if a connection is dropped and no reconnect is requested, the connection object is collected.

You can use a smart pointer around an access type like unique_ptr, but that would be an overkill. However, there are many implementations of smart pointers in Ada. E.g. reference-counted in the Simple Components (Simple components for Ada).

1 Like

Thank you, I’ll check your components.

So transfer of ownership is not really possible? Just store the access and trust in accessibility checks?

Of course, it is possible to implement in Ada. It is just totally useless in my view. A smart pointer in Ada is passed by reference, so there is no need to move anything.

If you want to disable assignment to prevent multiple references, declare it indefinite in the visible part.

An implementation of Move is trivial:

procedure Move (From, To : in out Handle) is
begin
   To := From;
   Invalidate (From);
end Move;

Usefulness of such an operation is evidently zero.

E.g. if you want to consume a smart pointer in a procedure Bar:

type Foo is record
   Reference : Handle;
   ... -- An awfully complex object
end record;

procedure Bar (X : in out Foo; Y : in out Handle) is
begin
   -- Configure X
   ...
   X.Reference := Y; -- Store the reference
   Invalidate (Y);   -- Surprise the caller by erasing Y
end Bar;

Accessibility checks it is a completely different issue.

Generics.

Generic
   Type Thing(<>) is private;
   with Function "+"( Left, Right : Thing ) return Thing is <>;
Function Example return Thing;

--Normal, w/ defaults:
Function Add is new Example( Integer );

--Nah, we're using "*" instead:
Function Mul is new Example( Integer, "*" ); 

Or, if you want something a little less trivial:

Generic
   Type Item(<>) is private;
   with Vector is new Ada.Containers.Indefinite_Vectors(Element_Type => Item , others => <> );
   with Procedure Execute( Object : in out Item ) is <>;
   Collection : in out Vector.Vector;
Procedure Visitor;

Procedure Visitor is
Begin
  For X of Collection loop
    Execute( X );
  end loop;
End Visitor;

Note that instantiation binds to the Vector “collection” and an operation to apply to all elements; this could be a PRINT procedure or a NORMALIZE procedure or whatever.