New features

An overview of the new features in IceRPC.

IceRPC adds a new protocol named icerpc that supports streaming. Streaming allows you to send a stream of bytes or a stream of Slice-defined types with your request or response with no or very little overhead.

This new icerpc protocol runs over multiplexed transports such as QUIC.

IceRPC offers a much more modular API than Ice.

For example, with Ice, most of the invocation logic is implemented by Communicator, and you can customize Communicator's behavior through configuration.

With IceRPC, the invocation logic consists of multiple objects that you compose to form an invocation pipeline: you include only what you need in this invocation pipeline, and you can easily insert your own custom logic in this pipeline.

Ice's API is based on the assumption that async APIs are well-suited for RPCs but hard to use and occasionally slower; as a result, Ice offers synchronous/blocking APIs in addition to async APIs, for ease of use and sometimes slightly better performance.

For example, with Ice, the Slice operation sayHello is mapped to two methods on proxies generated by the Slice compiler:

  • sayHello, which blocks the caller until the response is received and decoded
  • sayHelloAsync, which does not block the caller but returns the response through a task, future or callback depending on the language mapping

When you make an invocation, you can choose to call sayHello (easy to code but you block the calling thread) or sayHelloAsync (you don't block the calling thread but it's harder to obtain the response).

IceRPC takes a different, more modern approach: the async/await syntax makes async programming so easy there is no need to provide a redundant synchronous API. The async/await syntax also makes it clear that an RPC is a call that can take a while, throw exceptions, and should not be confused with a local call that completes very quickly.

With IceRPC:

  • methods that may take a while because their implementations can wait for I/O are async (e.g. proxy methods)
  • methods that never wait for I/O are synchronous (e.g. string-parsing methods)

With Ice, you can't separate Ice's IDL (Slice) from Ice: the Ice API is generated from local Slice definitions and you always use some Slice even when if you are only sending or receiving bytes.

Conversely, IceRPC sends and receives requests and responses with byte stream payloads, and doesn't know how these byte streams are encoded. This allows you to use IceRPC with Slice, or with another IDL such as Protobuf, or with no IDL at all.

IceRPC comes with a brand new Slice, with a new syntax, a new serialization format, and even a new file extension.

This new Slice is independent from IceRPC: you can naturally use it with IceRPC, but you can also use it without an RPC framework. Other RPC frameworks may provide support for this new Slice in the future.

The Ice compilation model for its Slice files is very much like C++: each Slice file needs to #include the Slice files with the definitions it depends on. You can use a forward declaration to introduce a type without fully defining it. And Slice files use the .ice file extension.

On the other hand, the compilation model for the new Slice files is more like C# and Java: the compilation uses a set of reference files specified as argument to the compiler, and there is no #include preprocessing directive or forward declarations. These new Slice files use the .slice extension.

Another difference is the syntax for parameters and fields: Ice's Slice uses a C-like syntax, while the new Slice syntax is more like Rust and Swift:

Slice definitions (old syntax)
enum File { A, B, C, D, E, F, G, H }
enum Rank { R1, R2, R3, R4, R5, R6, R7, R8 }
struct Position
{
File file;
Rank rank;
}
exception ChessException {}
interface ChessPiece
{
Position currentPosition();
void move(Position newPosition)
throws ChessException;
}
Slice definitions (new syntax)
interface ChessPiece {
// It's ok to use a type before defining it.
currentPosition() -> Position
move(newPosition: Position)
throws ChessException
}
compact struct Position {
file: File
rank: Rank
}
exception ChessException {}
enum File : uint8 { A, B, C, D, E, F, G, H }
enum Rank : uint8 { R1, R2, R3, R4, R5, R6, R7, R8 }

Was this page helpful?