Skip to main content
Skip table of contents

Using Ice and IceRPC Together

IceRPC is our new cutting-edge RPC framework, with a modular design, support for QUIC, an improved Slice language, and more.

While Ice is available in many languages, IceRPC language support is much more limited.

As an Ice user, there are broadly 3 ways you can adopt IceRPC:

  1. You port your existing Ice application to IceRPC, and then don’t look back.
    With this scenario, you can take full advantage of the new features provided by IceRPC. Your clients and servers can use the quic transport, or the tcp transport through the slic layer. And you can use new Slice features such as structs with optional fields, enums with fields, and custom types.
    Such a port requires a significant effort since the IceRPC API is very different from the Ice API: while the concepts remain similar, you’ll need to rework your entire communication layer.

  2. You port only a portion of your existing Ice application to IceRPC: some components (clients, servers) use Ice while others use IceRPC.

  3. You extend an existing Ice application with new components (clients, servers) implemented using IceRPC. The existing components remain Ice-based.

This page focuses on #2 and #3, where the Ice and IceRPC clients and servers communicate with each other using the Ice protocol and rely on the original Slice language understood by Ice.

Terminology

We tried to keep the IceRPC terminology in sync with the Ice terminology, but occasionally we found a better term or the Ice and IceRPC concepts aren’t an exact match.

Ice

IceRPC equivalent

Communicator

Connection cache (roughly)

Proxy

Proxy in the IceRPC+Slice add-on

Stringified proxy

Service address, service address URI

Endpoint

Server address, server address URI

Connection

Protocol connection (for advanced users)

Transport

Transport

Object adapter

Server and Router (roughly)

Ice object

Service

Servant

Service

Dispatcher

Dispatcher

Middleware

Middleware

Locator

(none)

Router

(none)

Properties

(none)

Protocol and Transport

IceRPC implements two RPC protocols: ice and icerpc, while Ice implements a single RPC protocol, the Ice Protocol. ice is identical to the Ice Protocol, so in your IceRPC components, make sure to use the ice protocol.

Ice and IceRPC have two transports in common: tcp and ssl. You can’t use udp or bt (since they are not supported by IceRPC), and you can’t use quic (since it’s not supported by Ice).

Missing Protocol Features

IceRPC’s implementation of the Ice Protocol (ice) is fairly complete, except it does not include two features:

  • batched requests

  • protocol compression

An Ice client can’t send batched requests to an IceRPC server (they won’t be received and processed). Likewise, an Ice client can’t send compressed requests to an IceRPC server.

Slice Migration

IceRPC includes support for an improved - but also fairly different - revision of the Slice language.

New Slice Syntax

The syntax of IceRPC’s Slice (New Slice) is quite different from the Ice’s Slice syntax (Original Slice):

CODE
// Original Slice syntax
interface Greeter
{
    string greet(string name);
}
CODE
// New Slice syntax
interface Greeter {
   greet(name: string) -> string   
}

Other than the formatting, you’ll notice that the New Slice syntax uses a Rust-like syntax for parameters and return values. Original Slice and New Slice have also different file structures:

  • Original Slice definitions are stored in .ice files and are processed as translation units with #include and other C/C++ preprocessing directives. The parsing is similar to C++, in particular, you can only use a type you’ve seen previously in your translation unit.

  • New Slice definitions are stored in .slicefiles and are processed in groups of source files and reference files. The parsing is similar to C# and Java, with no textual inclusion.

The slicec compiler provided with IceRPC only understands the New Slice syntax and file structure, while the slice2xxx compilers provided by Ice only understand the Original Slice syntax and its C++ like file structure.

Converting Slice Definitions with ice2slice

The ice2slice tool helps you convert Original Slice definitions in .ice files into New Slice definitions in .slice files. ice2slice is an Original Slice compiler that produces New Slice definitions.

For example:

BASH
ice2slice Greeter.ice

produces a file named Greeter.slice in the same directory.

Once you’ve converted your Slice definitions into .slice files, you should review these definitions and make the following adjustments:

Remove ? for parameter and field types, where appropriate

With Original Slice, a parameter or field with a proxy type or a class type is always nullable. Original Slice doesn’t provide a way to specify “this parameter is never null”. As a result, when you expect only non-null values, you need to write extra checking code to verify the argument or field you’ve received is not null. With New Slice, you can express “this parameter is never null” and the Slice library implements the check for you.

For example, let’s take this Original Slice interface:

CODE
// Original Slice
module ClearSky
{
    class AtmosphericConditions
    {
        ....
    }
    
    interface WeatherStation
    {
        /// Reports a new reading to the weather station.
        void report(string sensorId, AtmosphericConditions reading);
    }
}

We want the reading parameter to be always non-null: it doesn’t make sense to report a “null” reading. But this constraint can’t be expressed in Original Slice – a class parameter is always nullable.

ice2sliceconverts this definition into:

CODE
// New Slice generated by ice2slice
mode = Slice1

module ClearSky

class AtmosphericConditions {
    ...
}

interface WeatherStation {
    /// Reports a new reading to the weather station.
    report(sensorId: string, reading: AtmosphericConditions?)
}

And during the review process, we can make reading non-optional by removing the ?:

CODE
// New Slice after review
mode = Slice1

module ClearSky

interface WeatherStation {
    /// Reports a new reading to the weather station.
    report(sensorId: string, reading: AtmosphericConditions)
}

Remove unused proxy types

In Original Slice, whenever you define an interface Name, you implicitly define a proxy type, Name*. In practice, you rarely use the proxy types for all your interfaces. And indeed, many applications never transmit any proxies through Slice operations; for such applications, all these proxy types are superfluous.

When you define an interface Name in new Slice, you don’t define implicitly the corresponding proxy type. If you need this proxy type, you need to define it explicitly with a custom type: see https://docs.icerpc.dev/slice1/language-guide/using-proxies-as-slice-types.

ice2slice can’t detect which proxy types you need, so it produces proxy types for all interfaces in the Original Slice files “just in case”. For example, ice2slice generates the following definitions for the Greeter interface:

CODE
// New Slice
mode = Slice1

module VisitorCenter

/// Represents a simple greeter.
interface Greeter {
    /// Creates a personalized greeting.
    /// @param name: The name of the person to greet.
    /// @returns: The greeting.
    greet(name: string) -> string
}

[cs::type("VisitorCenter.GreeterProxy")]
custom GreeterProxy

We never use this GreeterProxy type, so it’s clearer (and cleaner) to delete it from the New Slice definitions.

Simplify with anonymous containers

In Original Slice, sequence and dictionary are always named types, such as:

CODE
// Original Slice

// Define Lines as a sequence of strings.
sequence<string> Lines;

ice2slice converts these named sequences and dictionaries into type aliases. For example, Lines becomes:

CODE
// New Slice
typealias Lines = Sequence<string>

This is correct and the most direct translation. Nevertheless, you may want to take advantage of the anonymous container support in New Slice and make your Slice definitions more compact. Continuing with our example:

CODE
// New Slice
interface File : Node {
    idempotent read() -> Lines
}

can be simplified into:

CODE
// New Slice
interface File : Node {
    idempotent read() -> Sequence<string> // anonymous sequence
}

Services

IceStorm

You can use IceRPC to implement an IceStorm publisher or an IceStorm subscriber. As far as the IceStorm server is concerned, publishers and subscribers are just regular clients and servers.

IceGrid

IceRPC provides client-side support for Locators and indirectly IceGrid: an IceRPC application can find objects in IceGrid-managed servers and send requests to these objects.

However, you cannot reimplement an IceGrid-managed server with IceRPC. IceGrid-managed servers rely heavily on Ice configuration files and IceRPC doesn’t support Ice configuration.

Glacier2

IceRPC does not currently provide support for Glacier2 clients. However, you can use IceRPC to implement a server called by Glacier2.

Discovery

The discovery plug-ins provided by Ice (IceDiscovery and IceLocatorDiscovery) rely on UDP multicast and IceRPC doesn’t provide any support for UDP. As a result, you can’t discover an IceRPC server using IceDiscovery, and an IceRPC client can’t use discover an Ice server that uses IceDiscovery.

See Also
JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.