Interfaces

12 min read

12 min read

11 min read

11 min read

9 min read

8 min read

11 min read

8 min read

11 min read

The central focus of Slice is on defining interfaces, for example:

Slice
module M
{
struct TimeOfDay
{
short hour; // 0 - 23
short minute; // 0 - 59
short second; // 0 - 59
}
interface Clock
{
TimeOfDay getTime();
void setTime(TimeOfDay time);
}
}

This definition defines an interface type called Clock. The interface supports two operations: getTime and setTime. Clients access an object supporting the Clock interface by invoking an operation on the proxy for the object: to read the current time, the client invokes the getTime operation; to set the current time, the client invokes the setTime operation, passing an argument of type TimeOfDay.

Invoking an operation on a proxy instructs the Ice runtime to send a message to the target object. The target object can be in another address space or can be collocated (in the same process) as the caller — the location of the target object is transparent to the client. If the target object is in another (possibly remote) address space, the Ice runtime invokes the operation via a remote procedure call; if the target is collocated with the client, the Ice runtime bypasses the network stack altogether to deliver the request more efficiently.

Note that nothing but operation definitions are allowed to appear inside an interface definition. In particular, you cannot define a type, an exception, or a field inside an interface. This does not mean that your object implementation cannot contain state — it can, but how that state is implemented (in the form of fields or otherwise) is hidden from the client and, therefore, need not appear in the object's interface definition.

A Slice interface defines the smallest grain of distribution in Ice: each Ice object has a unique identity (encapsulated in its proxy) that distinguishes it from other Ice objects; for communication to take place, you must invoke operations on an object's proxy. There is no other notion of an addressable entity in Ice. You cannot, for example, instantiate a Slice structure and have clients manipulate that structure remotely. To make the structure accessible, you must create an interface that allows clients to access the structure.

The partition of an application into interfaces therefore has profound influence on the overall architecture. Distribution boundaries must follow interface boundaries; you can spread the implementation of interfaces over multiple address spaces (and you can implement multiple interfaces in the same address space), but you cannot implement parts of interfaces in different address spaces.

The following Slice definition is legal:

Slice
interface Empty {}

The Slice compiler will compile this definition without complaint. An interesting question is: "why would I need an empty interface?". In most cases, empty interfaces are an indication of design errors. If you find yourself writing an empty interface definition, at least step back and think about the problem at hand; there may be a more appropriate design that expresses your intent more cleanly.

On the client side, a Slice interface maps to a C++ proxy class with member functions that correspond to the operations on that interface. Consider the following Slice interface:

Slice
module M
{
interface Simple
{
void op();
}
}

The Slice compiler generates the following definitions for use by the client:

C++
class SimplePrx : public Ice::Proxy<SimplePrx, Ice::ObjectPrx>
{
public:
// Constructors
SimplePrx(
const Ice::CommunicatorPtr& communicator,
std::string_view proxyString);
SimplePrx(const SimplePrx& other) noexcept;
SimplePrx(SimplePrx&& other) noexcept;
// Assignment operators.
SimplePrx& operator=(const SimplePrx& rhs) noexcept;
SimplePrx& operator=(SimplePrx&& rhs) noexcept;
// Member functions mapped from Slice operation op.
void op(const Ice::Context& = Ice::noExplicitContext) const;
[[nodiscard]] std::future<void> opAsync(
const Ice::Context& context = Ice::noExplicitContext) const;
std::function<void()> opAsync(
std::function<void()> response,
std::function<void(std::exception_ptr)> exception = nullptr,
std::function<void(bool)> sent = nullptr,
const Ice::Context& context = Ice::noExplicitContext) const;
};

Your client code interacts directly with the proxy class, M::SimplePrx in the example above. More generally, the generated proxy class for an interface in module M is the C++ proxy class M::<interface-name>Prx.

In the client's address space, an instance of the proxy class is the local ambassador for a remote instance of an Ice object that implements Simple and is known as a proxy class instance, or simply proxy. All the details about the server-side object, such as its address, what transport to use, and its object identity are encapsulated in that instance.

The Ice::Proxy template is a mix-in class that adds functionality to the proxy class via inheritance. It derives from the provided base proxy classes (here, only Ice::ObjectPrx):

C++
template<typename Prx, typename... Bases>
class Proxy : public virtual Bases...
{
// Helper functions for Prx
}

It’s an instance of the Curiously Recurring Template Pattern (CRTP).

Use the constructor of the generated class to create a proxy from a communicator and a “stringified” proxy. For example:

C++
M::SimplePrx simple{communicator, "simple:tcp -h localhost -p 4061"};

A proxy is a stack-allocated C++ object.

The proxy’s constructor does not allow you to create a “null” proxy. A nullable proxy - and by extension a null proxy - is a proxy held in a std::optional. For example:

C++
// A nullable Simple proxy, default-initialized to std::nullopt.
std::optional<M::SimplePrx> simple;

All generated proxy classes inherit indirectly from the Ice::ObjectPrx class, reflecting the fact that all Slice interfaces implicitly inherit from Object.

Inheritance relationships among Slice interfaces are maintained in the generated C++ classes. For example:

Slice
module M
{
interface A { ... }
interface B { ... }
interface C extends A, B { ... }
}

The generated code for CPrx reflects the inheritance hierarchy:

C++
namespace M
{
class CPrx : public Ice::Proxy<CPrx, APrx, BPrx>
{
...
};
}

Given a proxy for C, a client can invoke any operation defined for interface C, as well as any operation inherited from C's base interfaces.

The base proxy class Ice::ObjectPrx supports a variety of methods for customizing a proxy. Since proxies are immutable, each of these "factory methods" returns a copy of the original proxy that contains the desired modification. For example, you can obtain a proxy configured with a ten second invocation timeout as shown below:

C++
GreeterPrx greeter{communicator, "greeter:tcp -h localhost -p 4061"};
// Create a new GreeterPrx and assign it to greeter.
greeter = greeter.ice_invocationTimeout(10000);

The factory methods usually return a proxy of the same type as the current proxy, as in the example above.

The only exceptions are the factory methods ice_facet and ice_identity. Calls to either of these functions may produce a proxy for an object of an unrelated type, and you need to supply the desired proxy type when you call them. For example:

C++
GreeterPrx greeter{communicator, "greeter:tcp -h localhost -p 4061"};
GreeterAdminPrx greeterAdmin = greeter.ice_facet<GreeterAdminPrx>("admin");

On the server side, interfaces map to skeleton classes. A skeleton is a class that has a pure virtual member function for each operation on the corresponding interface. For example, consider our Slice definition for the Greeter interface:

Slice
module VisitorCenter
{
interface Greeter
{
string greet(string name);
}
}

The Slice compiler generates the following classes for this interface:

C++
namespace VisitorCenter
{
class Greeter : public virtual Ice::Object
{
public:
void dispatch(
IncomingRequest& request,
std::function<void(OutgoingResponse)> sendResponse) override;
virtual std::string greet(std::string name, const Ice::Current&) = 0;
// ...
};
class AsyncGreeter : public virtual Ice::Object
{
public:
void dispatch(
IncomingRequest& request,
std::function<void(OutgoingResponse)> sendResponse) override;
virtual void greetAsync(
std::string name,
std::function<void(std::string_view returnValue)> response,
std::function<void(std::exception_ptr)> exception,
const Ice::Current&) = 0;
// ...
};
// ...
}

The important points to note are:

  • As for the client side, Slice modules are mapped to C++ namespaces with the same name, so the skeleton class definition is nested in the namespace VisitorCenter.
  • The Slice compiler generates two skeleton classes: the default skeleton class has the same as the Slice interface (Greeter with our example), and the async skeleton class gets an additional Async prefix (AsyncGreeter).
  • Each skeleton class contains a pure virtual member function for each operation in the Slice interface.
  • Each skeleton class is an abstract base class because its member functions are pure virtual.
  • Each skeleton class inherits from Ice::Object (which forms the root of the Ice servant hierarchy).
  • Each skeleton class reimplements (overrides) the dispatch function defined on Ice::Object.

The Slice pseudo-interface Object is mapped to the Ice::Object class in C++:

C++
namespace Ice
{
class Object
{
public:
/// Dispatches an incoming request and returns the corresponding outgoing
/// response.
virtual void dispatch(
IncomingRequest& request,
std::function<void(OutgoingResponse)> sendResponse);
// ...
};
}

Object implements dispatch for the 4 operations on the Slice pseudo-interface Object: ice_ping, ice_isA, ice_id and ice_ids.

In order to provide an implementation for an Ice object, you must create a servant class that inherits from one of the generated skeleton classes. For example, to create a servant for the Greeter interface, you could write:

C++
#include "Greeter.h" // Slice-generated header
class Chatbot : public VisitorCenter::Greeter
{
public:
std::string greet(std::string name, const Ice::Current&) override;
};

Note that Chatbot derives from VisitorCenter::Greeter, one of the two generated skeleton classes.

As far as Ice is concerned, the Chatbot class must implement only a single member function: the pure virtual greet function that it inherits from its skeleton. This makes the servant class a concrete class that you can instantiate. You can add other member functions and data members as you see fit to support your implementation.

The async skeleton class is described in Asynchronous Method Dispatch (AMD) in C++.

On the client side, a Slice interface maps to a C# interface with methods that correspond to the operations on that interface. Consider the following Slice interface:

Slice
interface Simple
{
["cs:identifier:Op"]
void op();
}

The Slice compiler generates the following definition for use by the client:

C#
public partial interface SimplePrx : Ice.ObjectPrx
{
Task OpAsync(
Dictionary<string, string>? context = null,
IProgress<bool>? progress = null,
CancellationToken cancel = default);
// Synchronous "overload" provided for backwards compatibility.
void Op(Dictionary<string, string>? context = null);
}

As you can see, the compiler generates a proxy interfaceSimplePrx. In general, the generated name is <interface-name>Prx. If an interface is nested in a module M, the generated interface is part of namespace M, so the fully-qualified name is M.<interface-name>Prx.

In the client's address space, an instance of SimplePrx is the local ambassador for a remote instance of an Ice object that implements Simple and is known as a proxy instance. All the details about the server-side object, such as its address, what protocol to use, and its object identity are encapsulated in that instance.

For each Slice interface, apart from the proxy interface, the Slice-to-C# compiler creates a helper class: for an interface Simple, the name of the generated helper class is SimplePrxHelper.

This helper class provides the createProxy method. With our previous example:

C#
public class SimplePrxHelper : ...
{
public static SimplePrx createProxy(
Ice.Communicator communicator,
string proxyString) { ... }
}

Use createProxy to create a proxy from a communicator and a “stringified” proxy:

C#
SimplePrx simple = SimplePrxHelper.createProxy(
communicator, "simple:tcp -h localhost -p 4061");

All generated proxy interfaces inherit directly or indirectly from the Ice.ObjectPrx interface, reflecting the fact that all Slice interfaces implicitly inherit from Object.

Inheritance relationships among Slice interfaces are maintained in the generated C# classes. For example:

Slice
module M
{
interface A { ... }
interface B { ... }
interface C extends A, B { ... }
}

The generated code for CPrx reflects the inheritance hierarchy:

C#
namespace M;
public interface CPrx : APrx, BPrx
{
...
}

Given a proxy for C, a client can invoke any operation defined for interface C, as well as any operation inherited from C's base interfaces.

In addition to createProxy, the generated helper class provides two static methods for converting a proxy into a proxy of another type:

C#
public class SimplePrxHelper : ...
{
public static SimplePrx? uncheckedCast(Ice.ObjectPrx? proxy)
public static async Task<SimplePrx?> checkedCastAsync(
Ice.ObjectPrx proxy,
Dictionary<string, string>? context = null,
IProgress<bool>? progress = null,
CancellationToken cancel = default)
}

The helper’s uncheckedCast static method allows you to convert any proxy into the helper’s proxy type. For example:

C#
// Convert a SimplePrx into a WidgetPrx, even though the two types are unrelated.
WidgetPrx widget = WidgetPrxHelper.uncheckedCast(simple);

uncheckedCast is a local operation that always succeeds.

checkedCastAsync is a conditional cast of the proxy: this method makes a remote call to the target object to check if this object implements the proxy’s Slice interface. For example:

C#
// Call operation ice_isA on the Ice object to check if it implements Slice interface
// Widget.
WidgetPrx? widget = await WidgetPrxHelper.checkedCastAsync(simple);

If the target object implements the Slice interface, checkedCastAsync returns a non-null proxy, just like uncheckedCast. If the target object doesn’t implement this interface, checkedCastAsync returns null. checkedCastAsync can also throw an exception, for example if it cannot reach the remote object.

While checkedCastAsync sounds safer than uncheckedCast (you’re making an additional check before casting), in practice you know or should know the type of your proxies and calling checkedCastAsync is rarely necessary.

The base proxy interface ObjectPrx supports a variety of methods for customizing a proxy. Since proxies are immutable, each of these factory methods returns a copy of the original proxy that contains the desired modification. For example, you can obtain a proxy configured with a ten second invocation timeout as shown below:

C#
GreeterPrx greeter = GreeterPrxHelper.createProxy(...);
// Create a new GreeterPrx and assign it to greeter.
greeter = GreeterPrxHelper.uncheckedCast(greeter.ice_invocationTimeout(10000));

ice_invocationTimeout and other factory methods in C# return an Ice.ObjectPrx. You need to down-cast this proxy to the correct proxy type as shown above.

On the server side, interfaces map to skeleton classes. A skeleton is a class that has an abstract method for each operation on the corresponding interface. For example, consider our Slice definition for the Node interface:

Slice
module VisitorCenter
{
interface Greeter
{
["cs:identifier:Greet"]
string greet(string name);
}
}

The Slice compiler generates the following definitions for this interface:

C#
namespace VisitorCenter
{
public partial interface Greeter : Ice.Object
{
string Greet(string name, Ice.Current current);
}
public abstract partial class GreeterDisp_ : Greeter
{
public abstract string Greet(string name, Ice.Current current);
public ValueTask<Ice.OutgoingResponse> dispatchAsync(
Ice.IncomingRequest request)
{
...
}
}
public partial interface AsyncGreeter : Ice.Object
{
Task<string> GreetAsync(string name, Ice.Current current);
}
public abstract partial class AsyncGreeterDisp_ : AsyncGreeter
{
public abstract Task<string> GreetAsync(string name, Ice.Current current);
public ValueTask<Ice.OutgoingResponse> dispatchAsync(
Ice.IncomingRequest request)
{
...
}
}
}

The important points to note here are:

  • As for the client side, Slice modules are mapped to C# namespaces with the same name, so the skeleton class definitions are part of the VisitorCenter namespace.
  • For each Slice interface, the compiler generates two C# interfaces and two C# classes - the skeleton interfaces and classes.
  • Each skeleton class contains an abstract method for each operation in the Slice interface.
  • Each skeleton class implements the dispatchAsync method provided by Ice.Object: it dispatches incoming requests to the methods on the skeleton class based on the operation name received in the request.

The Slice pseudo-interface Object is mapped to the Ice.Object interface in C#:

C#
namespace Ice
{
public interface Object
{
public ValueTask<OutgoingResponse> dispatchAsync(IncomingRequest request)
{
...
}
...
}
}

Ice.Object provides a default dispatchAsync implementation for the 4 operations on the Slice pseudo-interface Object: ice_ping, ice_isA, ice_id and ice_ids.

In order to provide an implementation for an Ice object, you must create a servant class that inherits from one of the generated skeleton classes. For example, to create a servant for the Greeter interface, you could write:

C#
public class Chatbot : VisitorCenter.GreeterDisp_
{
public override string Greet(string name, Ice.Current current) =>
$"Hello, {name}!";
}

Note that Chatbot inherits from VisitorCenter.GreeterDisp_, one of the two skeleton classes.

As far as Ice is concerned, the Chatbot class must implement only a single method: the abstract method Name that it inherits from the skeleton class. This makes the servant class a concrete class that you can instantiate. You can add other methods and fields as you see fit to support your implementation.

The async skeleton class is described in Asynchronous Method Dispatch (AMD) in C#.

On the client side, a Slice interface maps to a Java interface with methods that correspond to the operations on that interface. Consider the following Slice interface:

Slice
interface Simple
{
void op();
}

The Slice compiler generates the following definition for use by the client:

Java
public interface SimplePrx extends com.zeroc.Ice.ObjectPrx {
void op();
void op(java.util.Map<String, String> context);
java.util.concurrent.CompletableFuture<Void> opAsync();
java.util.concurrent.CompletableFuture<Void> opAsync(
java.util.Map<String, String> context);
}

As you can see, the compiler generates a proxy interface SimplePrx. In general, the generated name is <interface-name>Prx.

In the client's address space, an instance of SimplePrx is the local ambassador for a remote instance of an Ice object that implements Simple and is known as a proxy instance. All the details about the server-side object, such as its address, what protocol to use, and its object identity are encapsulated in that instance.

The generated proxy interface provides a static createProxy method. With our previous example:

Java
public interface SimplePrx extends com.zeroc.Ice.ObjectPrx {
static SimplePrx createProxy(
com.zeroc.Ice.Communicator communicator, String proxyString)
...
}

Use createProxy to create a proxy from a communicator and a “stringified” proxy:

Java
SimplePrx simple = SimplePrx.createProxy(
communicator, "simple:tcp -h localhost -p 4061");

All generated proxy interfaces inherit directly or indirectly from the com.zeroc.Ice.ObjectPrx interface, reflecting the fact that all Slice interfaces implicitly inherit from Object.

Inheritance relationships among Slice interfaces are maintained in the generated Java interfaces. For example:

Slice
interface A { ... }
interface B { ... }
interface C extends A, B { ... }

The generated code for CPrx reflects the inheritance hierarchy:

Java
public interface CPrx extends APrx, BPrx {
...
}

Given a proxy for C, a client can invoke any operation defined for interface C, as well as any operation inherited from C's base interfaces.

In addition to createProxy, the generated proxy interfaces provides two static methods for converting a proxy into a proxy of another type:

Java
public interface SimplePrx extends com.zeroc.Ice.ObjectPrx {
static SimplePrx uncheckedCast(com.zeroc.Ice.ObjectPrx obj)
static SimplePrx checkedCast(com.zeroc.Ice.ObjectPrx obj)
}

The helper’s uncheckedCast static method allows you to convert any proxy into a proxy of this type. For example:

Java
// Convert a SimplePrx into a WidgetPrx, even though the two types are unrelated.
WidgetPrx widget = WidgetPrx.uncheckedCast(simple);

uncheckedCast is a local operation that always succeeds.

checkedCast is a conditional cast of the proxy: this method makes a remote call to the target object to check if this object implements the proxy’s Slice interface. For example:

Java
// Call operation ice_isA on the Ice object to check if it implements Slice interface
// Widget.
WidgetPrx widget = WidgetPrx.checkedCast(simple);

If the target object implements the Slice interface, checkedCast returns a non-null proxy, just like uncheckedCast. If the target object doesn’t implement this interface, checkedCast returns null. checkedCast can also throw an exception, for example if it cannot reach the remote object.

While checkedCast sounds safer than uncheckedCast (you’re making an additional check before casting), in practice you know or should know the type of your proxies and calling checkedCast is rarely necessary.

The base proxy interface ObjectPrx supports a variety of methods for customizing a proxy. Since proxies are immutable, each of these factory methods returns a copy of the original proxy that contains the desired modification. For example, you can obtain a proxy configured with a ten second invocation timeout as shown below:

Java
GreeterPrx greeter = GreeterPrx.createProxy(...);
// Create a new GreeterPrx and assign it to greeter.
greeter = greeter.ice_invocationTimeout(10000);

The factory methods usually return a proxy of the same type as the current proxy, as in the example above.

The only exceptions are the factory methods ice_facet and ice_identity. Calls to either of these methods may produce a proxy for an object of an unrelated type, and you need to cast the returned proxy. For example:

Java
GreeterPrx greeter = GreeterPrx.createProxy(...);
GreeterAdminPrx greeterAdmin = GreeterAdminPrx.uncheckedCast(
greeter.ice_facet("admin"));

On the server side, interfaces map to skeleton interfaces. A skeleton is an interface that defines a method for each operation on the corresponding Slice interface. For example, consider our Slice definition for the Greeter interface:

Slice
module VisitorCenter
{
interface Greeter
{
string greet(string name);
}
}

The Slice compiler generates the following definition for this interface:

Java
package VisitorCenter;
public interface Greeter extends com.zeroc.Ice.Object {
String greet(String name, com.zeroc.Ice.Current current);
@Override
default CompletionStage<com.zeroc.Ice.OutgoingResponse> dispatch(
com.zeroc.Ice.IncomingRequest request)
throws com.zeroc.Ice.UserException {
...
}
}
public interface AsyncGreeter extends com.zeroc.Ice.Object {
CompletionStage<String> greetAsync(
String name, com.zeroc.Ice.Current current);
@Override
default CompletionStage<com.zeroc.Ice.OutgoingResponse> dispatch(
com.zeroc.Ice.IncomingRequest request)
throws com.zeroc.Ice.UserException {
...
}
}

The important points to note here are:

  • As for the client side, Slice modules are mapped to Java packages with the same name, so the generated skeleton interface is part of the VisitorCenter package unless you remap it with the java:identifier metadata directive.

  • For each Slice interface <interface-name>, the compiler generates two Java interfaces that extend com.zeroc.Ice.Object (the skeleton interfaces).

  • Each skeleton interface contains an abstract method for each operation in the Slice interface.

  • Each skeleton interface reimplements (overrides) the dispatch method provided by com.zeroc.Ice.Object: it dispatches incoming requests to the methods on the skeleton based on the operation name received in the request.

The Slice pseudo-interface Object is mapped to the com.zeroc.Ice.Object interface in Java:

Java
package com.zeroc.Ice;
public interface Object {
default CompletionStage<OutgoingResponse> dispatch(IncomingRequest request)
throws UserException {
...
}
}

com.zeroc.Ice.Object provides a default dispatch implementation for the 4 operations on the Slice pseudo-interface Object: ice_ping, ice_isA, ice_id and ice_ids.

In order to provide an implementation for an Ice object, you must create a servant class that implements one of the generated skeleton interfaces. For example, to create a servant for the Greeter interface, you could write:

Java
class Chatbot implements Greeter {
@Override
public String greet(String name, com.zeroc.Ice.Current current) {
return "Hello, " + name + "!";
}
}

Note that Chatbot implements VisitorCenter.Greeter, one of the two skeleton interfaces.

As far as Ice is concerned, the Chatbot class must implement only a single method: the abstract method greet. This makes the servant class a concrete class that you can instantiate. You can add other methods and fields as you see fit to support your implementation.

The async skeleton interface is described in Asynchronous Method Dispatch (AMD) in Java.

On the client side, a Slice interface maps to a JavaScript class with methods that correspond to the operations on that interface. Consider the following Slice interface:

Slice
interface Simple
{
void op();
}

The Slice compiler generates code like:

JavaScript
class SimplePrx extends Ice.ObjectPrx {
op(context) { ... }
}
TypeScript
export namespace M {
export class SimplePrx extends Ice.ObjectPrx {
op(context?: Map<string, string>): Ice.AsyncResult<void>;
}
}

As you can see, the compiler generates a proxy type SimplePrx. In general, the generated name is <interface-name>Prx.

In the client's address space, an instance of SimplePrx is the local ambassador for a remote instance of an Ice object that implements Simple and is known as a proxy instance. All the details about the server-side object, such as its address, what protocol to use, and its object identity are encapsulated in that instance.

Use the constructor of the generated class to create a proxy from a communicator and a “stringified” proxy. For example:

TypeScript
const simple = new M.SimplePrx(
communicator,
"simple:tcp -h localhost -p 4061");

All generated proxy classes inherit directly from the Ice.ObjectPrx class, reflecting the fact that all Slice interfaces implicitly inherit from Object.

Slice interface inheritance is preserved in the generated TypeScript declarations, but not in the emitted JavaScript at runtime.

Slice
interface A { ... }
interface B { ... }
interface C extends A, B { ... }

The generated proxy class looks like:

JavaScript
M.CPrx = class extends Ice.ObjectPrx {
...
};

At runtime, the operations from APrx and BPrx are copied onto CPrx.prototype (no JavaScript class inheritance chain is used). This has two important consequences:

  • You can call any operation from Slice interface A or B on a CPrx instance, because those methods are present on CPrx.prototype.
  • However, c instanceof M.APrx (or M.BPrx) is false, since CPrx does not inherit from APrx or BPrx in JavaScript.

You can still pass a CPrx wherever an APrx or BPrx is expected—TypeScript’s generated declarations model this with implements, so the type checker accepts such usages even though the JavaScript runtime does not establish an inheritance relationship between the proxy classes.

TypeScript
export class CPrx extends Ice.ObjectPrx implements M.APrx, M.BPrx {
...
}

The generated proxy class provides two static methods for converting a proxy into a proxy of another type:

TypeScript
export class SimplePrx extends Ice.ObjectPrx {
static uncheckedCast(prx: Ice.ObjectPrx, facet?: string): SimplePrx;
static checkedCast(
prx: Ice.ObjectPrx,
facet?: string,
context?: Map<string, string>): Promise<SimplePrx | null>;
}

The uncheckedCast static method allows you to convert any proxy into a proxy of this type. For example:

TypeScript
// Convert a SimplePrx into a WidgetPrx, even though the two types are
// unrelated.
const widget = M.WidgetPrx.uncheckedCast(simple);

uncheckedCast is a local operation that always succeeds.

checkedCast is a conditional cast of the proxy: this method makes a remote call to the target object to check if this object implements the proxy’s Slice interface. For example:

TypeScript
// Call operation ice_isA on the Ice object to check if it implements
// Slice interface Widget.
const widget = await M.WidgetPrx.checkedCast(simple);

If the target object implements the Slice interface, checkedCast returns a new proxy, just like uncheckedCast. If the target object doesn’t implement this interface, checkedCast returns null. checkedCast can also throw an exception, for example if it cannot reach the remote object.

While checkedCast sounds safer than uncheckedCast (you’re making an additional check before casting), in practice you know or should know the type of your proxies and calling checkedCast is rarely necessary.

The base proxy class ObjectPrx supports a variety of methods for customizing a proxy. Since proxies are immutable, each of these factory methods returns a copy of the original proxy that contains the desired modification. For example, you can obtain a proxy configured with a ten second invocation timeout as shown below:

TypeScript
import { VisitorCenter } from "./Greeter.js"
let greeter = new VisitorCenter.GreeterPrx(
communicator,
"greeter:tcp -h localhost -p 4061");
greeter = greeter.ice_invocationTimeout(10000);

The factory methods usually return a proxy of the same type as the current proxy, as in the example above.

The only exceptions are the factory methods ice_facet and ice_identity. Calls to either of these methods may produce a proxy for an object of an unrelated type, and you need to cast the returned proxy. For example:

TypeScript
import { VisitorCenter } from "./Greeter.js"
const greeter = new VisitorCenter.GreeterPrx(
communicator,
"greeter:tcp -h localhost -p 4061");
const greeterAdmin = VisitorCenter.GreeterAdminPrx.uncheckedCast(
greeter.ice_facet("admin"));

On the server side, interfaces map to skeleton classes. A skeleton is a type that conceptually has an abstract method for each operation on the corresponding interface. For example, consider our Slice definition for the Node interface:

Slice
module Filesystem
{
interface Node
{
idempotent string name();
}
// ...
}

The Slice compiler generates the following definition for this interface:

JavaScript
class Node extends Ice.Object {
...
}
TypeScript
abstract class Node extends Ice.Object {
abstract name(current:Ice.Current): PromiseLike<string>|string;
}

The important points to note here are:

  • As for the client side, Slice modules are mapped to JavaScript objects with the same name, so the skeleton definitions are part of the Filesystem object.

  • For each Slice interface <interface-name>, the compiler generates a JavaScript class <interface-name> (Node in this example). This type extends Ice.Object and serves as the actual skeleton; it is the base type from which you derive your servant implementation.

In order to provide an implementation for an Ice object, you must create a servant class that inherits from the corresponding skeleton. For example, to create a servant for the Node interface, you could write:

TypeScript
class MNode extends Filesystem.Node {
_name:string;
constructor(name:string) {
super();
this._name = name;
}
name(current:Ice.Current) {
return this._name;
}
}

Note that MNode extends Filesystem.Node, the skeleton class.

As far as Ice is concerned, the MNode class must implement only a single method: the abstract method name. This makes the servant class a concrete class that you can instantiate. You can add other methods and fields as you see fit to support your implementation. For example, in the preceding definition, we added a _name field and a constructor.

A Slice interface maps to a MATLAB class with methods that correspond to the operations on that interface. Consider the following Slice interface:

Slice
interface Simple
{
void op();
}

The Slice compiler generates the following definition for use by the client:

MATLAB
classdef SimplePrx < Ice.ObjectPrx
methods
function op(obj, context)
% ...
end
function future = opAsync(obj, context)
% ...
end
end
% ...
end

As you can see, the compiler generates a proxy class SimplePrx. In general, the generated name is <interface-name>Prx. If an interface is nested in a module M, the generated class is part of namespace M, so the fully-qualified name is M.<interface-name>Prx.

In the client's address space, an instance of SimplePrx is the local ambassador for a remote instance of an Ice object that implements Simple and is known as a proxy instance. All the details about the server-side object, such as its address, what protocol to use, and its object identity are encapsulated in that instance.

Use the inherited constructor of the generated class to create a proxy from a communicator and a “stringified” proxy. For example:

MATLAB
simple = M.SimplePrx(communicator, 'simple:tcp -h localhost -p 4061');

All generated proxy classes inherit directly or indirectly from the Ice.ObjectPrx class, reflecting the fact that all Slice interfaces implicitly inherit from Object.

Inheritance relationships among Slice interfaces are maintained in the generated MATLAB classes. For example:

Slice
interface A { ... }
interface B { ... }
interface C extends A, B { ... }

The generated code for CPrx reflects the inheritance hierarchy:

MATLAB
classdef CPrx < APrx & BPrx
...
end

Given a proxy for C, a client can invoke any operation defined for interface C, as well as any operation inherited from C's base interfaces.

The generated proxy class provides two static methods for converting a proxy into a proxy of another type:

MATLAB
classdef SimplePrx < Ice.ObjectPrx
methods(Static)
function r = uncheckedCast(proxy)
% ...
end
function r = checkedCast(proxy, context)
% ...
end
end
% ...
end

The uncheckedCast static method allows you to convert any proxy into a proxy of this type. For example:

MATLAB
% Convert a SimplePrx into a WidgetPrx, even though the two types are unrelated.
widget = M.WidgetPrx.uncheckedCast(simple);

uncheckedCast is a local operation that always succeeds.

checkedCast is a conditional cast of the proxy: this method makes a remote call to the target object to check if this object implements the proxy’s Slice interface. For example:

MATLAB
% Call operation ice_isA on the Ice object to check if it implements Slice interface
% Widget.
widget = M.WidgetPrx.checkedCast(simple);

If the target object implements the Slice interface, checkedCast returns a new proxy, just like uncheckedCast. If the target object doesn’t implement this interface, checkedCast returns an empty array. checkedCast can also throw an exception, for example if it cannot reach the remote object.

While checkedCast sounds safer than uncheckedCast (you’re making an additional check before casting), in practice you know or should know the type of your proxies and calling checkedCast is rarely necessary.

The base proxy class ObjectPrx supports a variety of methods for customizing a proxy. Since proxies are immutable, each of these factory methods returns a copy of the original proxy that contains the desired modification. For example, you can obtain a proxy configured with a ten second invocation timeout as shown below:

MATLAB
greeter = GreeterPrx(communicator, 'simple:tcp -h localhost -p 4061');
% Create a new GreeterPrx and assign it to greeter.
greeter = greeter.ice_invocationTimeout(10000);

The proxy factory methods usually return a proxy of the same type as the current proxy, like in the example above.

The only exceptions are the factory methods ice_facet and ice_identity. Calls to either of these methods may produce a proxy for an object of an unrelated type, and you need to cast the returned proxy. For example:

MATLAB
greeter = GreeterPrx(communicator, 'simple:tcp -h localhost -p 4061');
greeterAdmin = GreeterAdminPrx.uncheckedCast(greeter.ice_facet('admin'));

Slice interfaces are implemented by instances of the Ice\ObjectPrx class in PHP. In the client's address space, an instance of ObjectPrx is the local ambassador for a remote Ice object in a server and is known as a proxy instance. All the details about the server-side object, such as its address, what protocol to use, and its object identity are encapsulated in that instance.

The PHP mapping for proxies differs from other Ice language mappings in that the ObjectPrx class is used to implement all Slice interfaces. The primary motivation for this design is minimizing the amount of code that is generated for each interface.

Nevertheless, you need to downcast a proxy to a specific Slice interface before you can invoke operations using this proxy, as described on this page.

For each Slice interface, apart from the proxy interface, the Slice-to-PHP compiler creates a helper class: for an interface Simple, the name of the generated helper class is SimplePrxHelper.

This helper class provides the createProxy method. With our previous example:

PHP
namespace M
{
class SimplePrxHelper
{
public static function createProxy($communicator, $proxyString)
}
}

Use createProxy to create a proxy from a communicator and a “stringified” proxy. For example:

PHP
$simple = M\SimplePrxHelper::createProxy(
$communicator,
'simple:tcp -h localhost -p 4061');
Slice
interface A { ... }
interface B { ... }
interface C extends A, B { ... }

Given a proxy that has been down-casted to C, a client can invoke any operation defined for interface C, as well as any operation inherited from C's base interfaces.

In addition to createProxy, the generated helper class provides two static methods for converting a proxy into a proxy of another type:

PHP
namespace M
{
class SimplePrxHelper
{
public static function uncheckedCast($proxy, $facet=null)
public static function checkedCast($proxy, ...$args)
}
}

The helper’s uncheckedCast static method allows you to convert any proxy into a proxy of this type. For example:

PHP
// Convert a SimplePrx into a WidgetPrx, even though the two types are unrelated.
$widget = M\WidgetPrxHelper::uncheckedCast($simple);

uncheckedCast is a local operation that always succeeds.

checkedCast is a conditional cast of the proxy: this method makes a remote call to the target object to check if this object implements the proxy’s Slice interface. For example:

PHP
// Call operation ice_isA on the Ice object to check if it implements Slice interface
// Widget.
$widget = M\WidgetPrxHelper::checkedCast($simple);

If the target object implements the Slice interface, checkedCast returns a non-null proxy, just like uncheckedCast. If the target object doesn’t implement this interface, checkedCast returns null. checkedCast can also throw an exception, for example if it cannot reach the remote object.

While checkedCast sounds safer than uncheckedCast (you’re making an additional check before casting), in practice you know or should know the type of your proxies and calling checkedCast is rarely necessary.

The base proxy interface ObjectPrx supports a variety of methods for customizing a proxy. Since proxies are immutable, each of these factory methods returns a copy of the original proxy that contains the desired modification. For example, you can obtain a proxy configured with a ten second invocation timeout as shown below:

PHP
$greeter = VisitorCenter\GreeterPrxHelper::createProxy(...);
// Create a new GreeterPrx and assign it to $greeter.
$greeter = $greeter->ice_invocationTimeout(10000);

The factory methods usually return a proxy of the same type as the current proxy, as in the example above.

The only exceptions are the factory methods ice_facet and ice_identity. Calls to either of these methods may produce a proxy for an object of an unrelated type, and you need to cast the returned proxy. For example:

PHP
$greeter = VisitorCenter\GreeterPrxHelper::createProxy(...);
$greeterAdmin = VisitorCenter\GreeterAdminPrxHelper::uncheckedCast(
$greeter->ice_facet("admin"));

On the client side, a Slice interface maps to a Python class with methods that correspond to the operations on that interface. Consider the following Slice interface:

Slice
interface Simple
{
void op();
}

The Python mapping generates the following definition for use by the client:

Python
class SimplePrx(Ice.ObjectPrx):
def op(self, context: dict[str, str] | None = None) -> None:
...
def opAsync(self, context: dict[str, str] | None = None) -> Awaitable[None]:
...

As you can see, the compiler generates a proxy class SimplePrx. In general, the generated name is <interface-name>Prx.

In the client's address space, an instance of SimplePrx is the local ambassador for a remote instance of an Ice object that implements Simple and is known as a proxy instance. All the details about the server-side object, such as its address, what protocol to use, and its object identity are encapsulated in that instance.

All generated proxy classes inherit directly or indirectly from the Ice.ObjectPrx class, reflecting the fact that all Slice interfaces implicitly inherit from Object.

Use the constructor of the generated proxy class to create a proxy from a communicator and a “stringified” proxy. For example:

Python
import M
simple = M.SimplePrx(communicator, "simple:tcp -h localhost -p 4061")

__init__ is inherited from Ice.ObjectPrx.

Inheritance relationships among Slice interfaces are maintained in the generated Python classes. For example:

Slice
interface A { ... }
interface B { ... }
interface C extends A, B { ... }

The generated code for CPrx reflects the inheritance hierarchy:

Python
class CPrx(APrx, BPrx):
...

Given a proxy for C, a client can invoke any operation defined for interface C, as well as any operation inherited from C's base interfaces.

The Python mapping for a proxy also generates 3 static methods for converting a proxy into a proxy of another type:

Python
class SimplePrx(Ice.ObjectPrx):
@staticmethod
def uncheckedCast(proxy, facet=None)
@staticmethod
def checkedCastAsync(proxy, facet=None, context=None)
@staticmethod
def checkedCast(proxy, facet=None, context=None)

uncheckedCast allows you to convert any proxy into a SimplePrx proxy. For example:

Python
# Convert a SimplePrx into a WidgetPrx, even though the two types are unrelated.
widget = WidgetPrx.uncheckedCast(simple)

uncheckedCast is a local operation that always succeeds.

checkedCastAsync is a conditional cast of the proxy: this method makes a remote call to the target object to check if this object implements the proxy’s Slice interface. For example:

Python
# Call operation ice_isA on the Ice object to check if it implements Slice interface
# Widget.
widget = await WidgetPrx.checkedCastAsync(simple)

If the target object implements the Slice interface, checkedCastAsync returns a non-null proxy, just like uncheckedCast. If the target object doesn’t implement this interface, checkedCastAsync returns None. checkedCastAsync can also throw an exception, for example if it cannot reach the remote object.

While checkedCastAsync sounds safer than uncheckedCast (you’re making an additional check before casting), in practice you know or should know the type of your proxies and calling checkedCastAsync is rarely necessary.

checkedCast is the synchronous equivalent of checkedCastAsync: it blocks the caller until the response it receives. You should prefer async methods over their their synchronous equivalent when making remote calls with Ice.

The base proxy class ObjectPrx supports a variety of methods for customizing a proxy. Since proxies are immutable, each of these factory methods returns a copy of the original proxy that contains the desired modification. For example, you can obtain a proxy configured with a ten second invocation timeout as shown below:

Python
greeter = VisitorCenter.GreeterPrx(communicator, "greeter:tcp -h localhost -p 4061")
# Create a new GreeterPrx and assign it to greeter.
greeter = greeter.ice_invocationTimeout(10000)

The factory methods usually return a proxy of the same type as the current proxy, as in the example above.

The only exceptions are the factory methods ice_facet and ice_identity. Calls to either of these methods may produce a proxy for an object of an unrelated type, and you need to cast the returned proxy. For example:

Python
greeter = VisitorCenter.GreeterPrx(communicator, "greeter:tcp -h localhost -p 4061")
greeterAdmin = VisitorCenter.GreeterAdminPrx.uncheckedCast(
greeter.ice_facet("admin"))

On the server side, interfaces map to skeleton classes. A skeleton is an abstract base class from which you derive your servant class and define a method for each operation on the corresponding interface. For example, consider our Slice definition for the Node interface:

Slice
module Filesystem
{
interface Node
{
idempotent string name();
}
// ...
}

The Python mapping generates the following definition for this interface:

Python
class Node(Ice.Object, ABC):
@abstractmethod
def name(self, current: Current) -> str | Awaitable[str]:
pass

The important points to note here are:

  • As for the client side, Slice modules are mapped to Python packages with the same name, so the skeleton class definitions are part of the Filesystem package.
  • The name of the skeleton class is the same as the Slice interface (Node).
  • The skeleton class is an abstract base class with an abstract method for each operation defined in the Slice interface.
  • The skeleton class inherits from Ice.Object (which forms the root of the Ice object hierarchy).

The Slice pseudo-interface Object is mapped to the Ice.Object class in Python.

In order to provide an implementation for an Ice object, you must create a servant class that inherits from the corresponding skeleton class. For example, to create a servant for the Node interface, you could write:

Python
from Filesystem import Node
import Ice
class MNode(Node):
def __init__(self, name: str):
self._name = name
def name(self, _: Ice.Current) -> str:
return self._name

Note that MNode implements Node, the skeleton class.

As far as Ice is concerned, the MNode class must implement only a single method: the abstract method name. This makes the servant class a concrete class that you can instantiate. You can add other methods and attributes as you see fit to support your implementation. For example, in the preceding definition, we added a _name instance attribute and an initializer.

On the client side, a Slice interface maps to a Ruby class with methods that correspond to the operations on those interfaces. Consider the following Slice interface:

Slice
interface Simple
{
void op();
}

The Ruby mapping generates the following definition for use by the client:

Ruby
module SimplePrx_mixin
def op(context=nil)
...
end
end
class SimplePrx < Ice::ObjectPrx
include SimplePrx_mixin
end

In the client's address space, an instance of SimplePrx is the local ambassador for a remote instance of an Ice object that implements Simple and is known as a proxy instance. All the details about the server-side object, such as its address, what protocol to use, and its object identity are encapsulated in that instance.

Use the constructor of the generated class to create a proxy from a communicator and a “stringified” proxy. For example:

Ruby
simple = M::SimplePrx.new(communicator, "simple:tcp -h localhost -p 4061")

All generated proxy classes inherit directly or indirectly from the Ice::ObjectPrx class, reflecting the fact that all Slice interfaces implicitly inherit from Object.

Inheritance relationships among Slice interfaces are maintained in the generated Ruby classes. For example:

Slice
interface A { ... }
interface B { ... }
interface C extends A, B { ... }

The generated code for CPrx uses mixins to include the operations of APrx and BPrx:

Ruby
module CPrx_mixin
include APrx_mixin
include BPrx_mixin
end
class CPrx < Ice::ObjectPrx
include CPrx_mixin
end

Given a proxy for C, a client can invoke any operation defined for interface C, as well as any operation inherited from C's base interfaces.

All proxy classes provide 2 class methods that allow you to convert any proxy into a proxy of this type.

Ruby
def uncheckedCast(proxy, facet: nil)
...
end
def checkedCast(proxy, facet: nil, context: nil)
...
end

The uncheckedCast method allows you to convert any proxy into a proxy of this type. For example:

Ruby
# Convert a SimplePrx into a WidgetPrx, even though the two types are unrelated.
widget = M::WidgetPrx.uncheckedCast(simple)

uncheckedCast is a local operation that always succeeds.

checkedCast is a conditional cast of the proxy: this method makes a remote call to the target object to check if this object implements the proxy’s Slice interface. For example:

Ruby
# Call operation ice_isA on the Ice object to check if it implements Slice interface
# Widget.
widget = M::WidgetPrx.checkedCast(simple)

If the target object implements the Slice interface, checkedCast returns a new proxy, just like uncheckedCast. If the target object doesn’t implement this interface, checkedCast returns nil. checkedCast can also throw an exception, for example if it cannot reach the remote object.

While checkedCast sounds safer than uncheckedCast (you’re making an additional check before casting), in practice you know or should know the type of your proxies and calling checkedCast is rarely necessary.

The base proxy class ObjectPrx supports a variety of methods for customizing a proxy. Since proxies are immutable, each of these factory methods returns a copy of the original proxy that contains the desired modification. For example, you can obtain a proxy configured with a ten second invocation timeout as shown below:

Ruby
greeter = VisitorCenter::GreeterPrx.new(
communicator,
"greeter:tcp -h localhost -p 4061")
# Create a new GreeterPrx and assign it to greeter.
greeter = greeter.ice_invocationTimeout(10000)

The proxy factory methods usually return a proxy of the same type as the current proxy, like in the example above.

The only exceptions are the factory methods ice_facet and ice_identity. Calls to either of these methods may produce a proxy for an object of an unrelated type, and you need to cast the returned proxy. For example:

Ruby
greeter = VisitorCenter::GreeterPrx.new(
communicator, "greeter:tcp -h localhost -p 4061")
greeterAdmin = VisitorCenter::GreeterAdminPrx.uncheckedCast(
greeter.ice_facet("admin"))

On the client side, a Slice interface maps to an empty Swift protocol. A public extension of this protocol provides two methods for each Slice operation of your Slice interface.

Consider the following Slice interface:

Slice
module M
{
interface Simple
{
void op();
}
}

The Slice compiler generates the following definitions for use by the client:

Swift
// in module M
public protocol SimplePrx: Ice.ObjectPrx {}
public extension SimplePrx {
func op(context: Ice.Context? = nil) async throws {
...
}
}

As you can see, the compiler generates a proxy protocol SimplePrx. In general, the generated name is <interface-name>Prx.

In the client's address space, an instance of SimplePrx is the local ambassador for a remote instance of an Ice object that implements Simple and is known as a proxy instance. All the details about the server-side object, such as its address, what protocol to use, and its object identity are encapsulated in that instance.

For each proxy, the Slice compiler generate a makeProxy factory function in the same Swift module. With our previous example:

Swift
public protocol SimplePrx: Ice.ObjectPrx {}
public func makeProxy(communicator: Ice.Communicator,
proxyString: String,
type: SimplePrx.Protocol) throws -> SimplePrx {
...
}

Call makeProxy to create a proxy from a communicator and a “stringified” proxy:

Swift
let simple = try makeProxy(
communicator: communicator, proxyString: "simple:tcp -h localhost -p 4061",
type: SimplePrx.self)

All generated proxy protocols inherit directly or indirectly from the Ice.ObjectPrx protocol, reflecting the fact that all Slice interfaces implicitly inherit from Object.

Inheritance relationships among Slice interfaces are maintained in the generated Swift protocols. For example:

Slice
module M
{
interface A { ... }
interface B { ... }
interface C extends A, B { ... }
}

The generated code for CPrx reflects the inheritance hierarchy:

Swift
public protocol CPrx: APrx, BPrx {}

Given a proxy for C, a client can invoke any operation defined for interface C, as well as any operation inherited from C's base interfaces.

For each proxy, the Slice compiler generate 2 helper functions that allow you to convert any proxy into a proxy of this type. With our Simple example:

Swift
public protocol SimplePrx: Ice.ObjectPrx {}
public func uncheckedCast(prx: Ice.ObjectPrx,
type: SimplePrx.Protocol,
facet: String? = nil) -> SimplePrx {
...
}
public func checkedCast(prx: Ice.ObjectPrx,
type: SimplePrx.Protocol,
facet: String? = nil,
context: Ice.Context? = nil) async throws -> SimplePrx? {
...
}

The uncheckedCast function allows you to convert a proxy into another proxy. For example:

Swift
// Convert a SimplePrx into a WidgetPrx, even though the two types are unrelated.
let widget = uncheckedCast(prx: simple, type: WidgetPrx.self)

uncheckedCast is a local operation that always succeeds.

checkedCast is a conditional cast of the proxy: this function makes a remote call to the target object to check if this object implements the proxy’s Slice interface. For example:

Swift
// Call operation ice_isA on the Ice object to check if it implements Slice interface
// Widget.
let widget = try await checkedCast(prx: simple, type: WidgetPrx.self)

If the target object implements the Slice interface, checkedCast returns a non-nil proxy, just like uncheckedCast. If the target object doesn’t implement this interface, checkedCast returns nil. checkedCast can also throw an exception, for example if it cannot reach the remote object.

While checkedCast sounds safer than uncheckedCast (you’re making an additional check before casting), in practice you know or should know the type of your proxies and calling checkedCast is rarely necessary.

The base proxy interface ObjectPrx supports a variety of methods for customizing a proxy. Since proxies are immutable, each of these factory methods returns a copy of the original proxy that contains the desired modification. For example, you can obtain a proxy configured with a ten second invocation timeout as shown below:

Swift
var greeter = try makeProxy(communicator: ..., proxyString: ..., type: GreeterPrx.self)
// Create a new GreeterPrx and assign it to greeter.
greeter = greeter.ice_invocationTimeout(10000)

The factory methods usually return a proxy of the same type as the current proxy, as in the example above.

The only exceptions are the factory methods ice_facet and ice_identity. Calls to either of these methods may produce a proxy for an object of an unrelated type, and you need to cast the returned proxy. For example:

Swift
let greeter = try makeProxy(communicator: ..., proxyString: ..., type: GreeterPrx.self)
let greeterAdmin = uncheckedCast(
prx: greeter.ice_facet("admin"), type: GreeterAdminPrx.self)

The server-side mapping for interfaces provides an up-call API for the Ice runtime: by implementing instance methods in a servant class, you provide the hook that gets the thread of control from the Ice server-side runtime into your application code.

On the server side, interfaces map to skeleton protocols. A skeleton protocol specifies an instance method for each operation on the corresponding Slice interface. For example, consider our Slice definition for the Node interface:

Slice
module Filesystem
{
interface Node
{
idempotent string name();
}
// ...
}

The Slice compiler generates the following definitions for this interface:

Swift
// Skeleton protocol
public protocol Node: Ice.Dispatcher {
func name(current: Ice.Current) async throws -> Swift.String
}
extension Node {
public func dispatch(
_ request: sending Ice.IncomingRequest) async throws ->
Ice.OutgoingResponse {
...
}
}

The important points to note here are:

  • For each Slice interface <interface-name>, the compiler generates a Swift protocol <interface-name> (Node in this example).
  • This skeleton protocol extends Ice.Dispatcher, and the Slice compiler generates an extension for the skeleton protocol that implements dispatch (Dispatcher’s only method).
  • The base servant protocol in Swift is Ice.Dispatcher; skeleton protocols do not derive from Ice.Object.

In order to provide an implementation for an Ice object, you must create a servant struct, class or actor that adopts the corresponding skeleton protocol. For example, to create a servant for the Node interface, you could write:

Swift
struct MNode: Node {
private let name: String
init(name: String) {
self.name = name
}
func name(current _: Ice.Current) -> String {
name
}
}

Note that MNode adopts Node, the skeleton protocol.

As far as Ice is concerned, the MNode struct must implement only a single method: the name method from its skeleton. This makes the servant struct a concrete type that can be instantiated. You can add other methods and fields as you see fit to support your implementation. For example, in the preceding definition, we added a name field and an initializer.