In addition to providing the built‑in basic types, Slice allows you to define complex types: enumerations, structures, sequences, and dictionaries.
enum Fruit { Apple, Pear, Orange };
This definition introduces a type named Fruit that becomes a new type in its own right. Slice does not define how ordinal values are assigned to enumerators. For example, you cannot assume that the enumerator
Orange will have the value 2 in different implementation languages. Slice guarantees only that the ordinal values of enumerators increase from left to right, so
Apple compares less than
Pear in all implementation languages.
Unlike C++, Slice does not permit you to control the ordinal values of enumerators (because many implementation languages do not support such a feature):
enum Fruit { Apple = 0, Pear = 7, Orange = 2 }; // Syntax error
In practice, you do not care about the values used for enumerators as long as you do not transmit the
ordinal value of an enumerator between address spaces. For example, sending the value 0 to a server to mean
Apple can cause problems because the server may not use 0 to represent
Apple. Instead, simply send the value
Apple itself. If
Apple is represented by a different ordinal value in the receiving address space, that value will be appropriately translated by the Ice run time.
enum Fruit { Apple, Pear, Orange };
enum ComputerBrands { Apple, IBM, Sun, HP }; // Apple redefined
struct TimeOfDay {
short hour; // 0 ‑ 23
short minute; // 0 ‑ 59
short second; // 0 ‑ 59
};
As in C++, this definition introduces a new type called TimeOfDay. Structure definitions form a namespace, so the names of the structure members need to be unique only within their enclosing structure.
Data member definitions using a named type are the only construct that can appear inside a structure. It is impossible to, for example, define a structure inside a structure:
struct TwoPoints {
struct Point { // Illegal!
short x;
short y;
};
Point coord1;
Point coord2;
};
This rule applies to Slice in general: type definitions cannot be nested (except for modules, which do support nesting—see
Section 4.6). The reason for this rule is that nested type definitions can be difficult to implement for some target languages and, even if implementable, greatly complicate the scope resolution rules. For a specification language, such as Slice, nested type definitions are unnecessary—you can always write the above definitions as follows (which is stylistically cleaner as well):
struct Point {
short x;
short y;
};
struct TwoPoints { // Legal (and cleaner!)
Point coord1;
Point coord2;
};
struct Location {
string name;
Point pt;
bool display = true;
string source = "GPS";
};
The legal syntax for literal values is the same as for Slice constants (see Section 4.9.5). The language mapping guarantees that data members are initialized to their declared default values using a language-specific mechanism.
A sequence can be empty — that is, it can contain no elements, or it can hold any number of elements up to the memory limits of your platform.
Sequences are used to model a variety of collections, such as vectors, lists, queues, sets, bags, or trees. (It is up to the application to decide whether or not order is important; by discarding order, a sequence serves as a set or bag.)
One particular use of sequences has become idiomatic, namely, the use of a sequence to indicate an optional value. For example, we might have a
Part structure that records the details of the parts that go into a car. The structure could record things such as the name of the part, a description, weight, price, and other details. Spare parts commonly have a serial number, which we can model as a
long value. However, some parts, such as simple screws, often do not have a serial number, so what are we supposed to put into the serial number field of a screw? There are a number of options for dealing with this situation:
Strings come with their own built‑in sentinel value, namely, the empty string so we can use an empty string to indicate the “no serial number” case. This is workable, but leaves a bad taste in most people’s mouth: we should not have to change the natural data type of something to
string just so we get a sentinel value.
struct Part {
string name;
string description;
// ...
bool serialIsValid; // true if part has serial number
long serialNumber;
};
sequence<long> SerialOpt;
struct Part {
string name;
string description;
// ...
SerialOpt serialNumber; // optional: zero or one element
};
By convention, the Opt suffix is used to indicate that the sequence is used to model an optional value. If the sequence is empty, the value is obviously not there; if it contains a single element, that element is the value. The obvious drawback of this scheme is that someone could put more than one element into the sequence. This could be rectified by adding a special-purpose Slice construct for optional values. However, optional values are not used frequently enough to justify the complexity of adding a dedicated language feature. (As we will see in
Section 4.11, you can also use class hierarchies to model optional fields.)
struct Employee {
long number;
string firstName;
string lastName;
};
dictionary<long, Employee> EmployeeMap;
This definition creates a dictionary named EmployeeMap that maps from an employee number to a structure containing the details for an employee. Whether or not the key type (the employee number, of type
long in this example) is also part of the value type (the
Employee structure in this example) is up to you—as far as Slice is concerned, there is no need to include the key as part of the value.
Dictionaries can be used to implement sparse arrays, or any lookup data structure with non-integral key type. Even though a sequence of structures containing key–value pairs could be used to model the same thing, a dictionary is more appropriate:
•
At the programming language level, sequences are implemented as vectors (or possibly lists), that is, they are not well suited to model sparsely populated domains and require a linear search to locate an element with a particular value. On the other hand, dictionaries are implemented as a data structure (typically a hash table or red-black tree) that supports efficient searching in
O(log
n) average time or better.
Complex nested types, such as nested structures, sequences, or dictionaries, and floating-point types (
float and
double) cannot be used as the key type. Complex nested types are disallowed because they complicate the language mappings for dictionaries, and floating-point types are disallowed because representational changes of values as they cross machine boundaries can lead to ill-defined semantics for equality.
const bool AppendByDefault = true;
const byte LowerNibble = 0x0f;
const string Advice = "Don't Panic!";
const short TheAnswer = 42;
const double PI = 3.1416;
enum Fruit { Apple, Pear, Orange };
const Fruit FavoriteFruit = Pear;
const byte TheAnswer = 42;
const byte TheAnswerInOctal = 052;
const byte TheAnswerInHex = 0x2A; // or 0x2a
Be aware that, if you interpret byte as a number instead of a bit pattern, you may get different results in different languages. For example, for C++,
byte maps to
unsigned char whereas, for Java,
byte maps to
byte, which is a signed type.
const long Wrong = 0u; // Syntax error
const long WrongToo = 1000000L; // Syntax error
const float P1 = ‑3.14f; // Integer & fraction, with suffix
const float P2 = +3.1e‑3; // Integer, fraction, and exponent
const float P3 = .1; // Fraction part only
const float P4 = 1.; // Integer part only
const float P5 = .9E5; // Fraction part and exponent
const float P6 = 5e2; // Integer part and exponent
const string AnOrdinaryString = "Hello World!";
const string DoubleQuote = "\"";
const string TwoSingleQuotes = "'\'"; // ' and \' are OK
const string Newline = "\n";
const string CarriageReturn = "\r";
const string HorizontalTab = "\t";
const string VerticalTab = "\v";
const string FormFeed = "\f";
const string Alert = "\a";
const string Backspace = "\b";
const string QuestionMark = "\?";
const string Backslash = "\\";
const string OctalEscape = "\007"; // Same as \a
const string HexEscape = "\x07"; // Ditto
const string UniversalCharName = "\u03A9"; // Greek Omega
const string MSG1 = "Hello World!";
const string MSG2 = "Hello" " " "World!"; // Same message
/*
* Escape sequences are processed before concatenation,
* so the string below contains two characters,
* '\xa' and 'c'.
*/
const string S = "\xa" "c";
const string nullString = 0; // Illegal!