Table of Contents Previous Next
Logo
The Slice Language : 4.9 User-Defined Types
Copyright © 2003-2009 ZeroC, Inc.

4.9 User-Defined Types

In addition to providing the built‑in basic types, Slice allows you to define complex types: enumerations, structures, sequences, and dictionaries.

4.9.1 Enumerations

A Slice enumerated type definition looks like the C++ version:
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.
As with C++, Slice enumerators enter the enclosing namespace, so the following is illegal:
enum Fruit { Apple, Pear, Orange };
enum ComputerBrands { Apple, IBM, Sun, HP };    // Apple redefined
Slice does not permit empty enumerations.

4.9.2 Structures

Slice supports structures containing one or more named members of arbitrary type, including user-defined complex types. For example:
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;
};

4.9.3 Sequences

Sequences are variable-length collections of elements:
sequence<Fruit> FruitPlatter;
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 can contain elements that are themselves sequences. This arrangement allows you to create lists of lists:
sequence<FruitPlatter> FruitBanquet;
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 a number of options for dealing with this situation:
• Use a sentinel value, such as zero, to indicate the "no serial" number condition.
This approach is workable, provided that a sentinel value is actually available. While it may seem unlikely that anyone would use a serial number of zero for a part, it is not impossible. And, for other values, such as a temperature value, all values in the range of their type can be legal, so no sentinel value is available.
• Change the type of the serial number from long to string.
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.
• Add an indicator as to whether the contents of the serial number are valid:
struct Part {
    string name;
    string description;
    // ...
    bool   serialIsValid;  // true if part has serial number
    long   serialNumber;
};
This is distasteful to most people and guaranteed to get you into trouble eventually: sooner or later, some programmer will forget to check whether the serial number is valid before using it and create havoc.
• Use a sequence to model the optional field.
This technique uses the following convention:
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.)

4.9.4 Dictionaries

A dictionary is a mapping from a key type to a value type. For example:
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:
• A dictionary clearly signals the intent of the designer, namely, to provide a mapping from a domain of values to a range of values. (A sequence of structures of key–value pairs does not signal that same intent as clearly.)
• 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.
The key type of a dictionary need not be an integral type. For example, we could use the following definition to translate the names of the days of the week:
dictionary<string, string> WeekdaysEnglishToGerman;
The server implementation would take care of initializing this map with the key–value pairs MondayMontag, TuesdayDienstag, and so on.
The value type of a dictionary can be any user-defined type. However, the key type of a dictionary is limited to one of the following types:
• Integral types (byte, short, int, long, bool, and enumerated types)
• string
• structures containing only data members of integral type or string
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.

4.9.5 Constant Definitions and Literals

Slice allows you to define constants. Constant definitions must be of one of the following types:
• An integral type (bool, byte, short, int, long, or an enumerated type)
• float or double
• string
Here are a few examples:
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;
The syntax for literals is the same as for C++ and Java (with a few minor exceptions):
• Boolean constants can only be initialized with the keywords false and true. (You cannot use 0 and 1 to represent false and true.)
• As for C++, integer literals can be specified in decimal, octal, or hexadecimal notation. For example:
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.
Note that suffixes to indicate long and unsigned constants (l, L, u, U, used by C++) are illegal:
const long Wrong = 0u;          // Syntax error
const long WrongToo = 1000000L; // Syntax error
The value of an integer literal must be within the range of its constant type, as shown in Table 4.1 on page 102; otherwise the compiler will issue a diagnostic.
• Floating-point literals use C++ syntax, except that you cannot use an l or L suffix to indicate an extended floating-point constant; however, f and F are legal (but are ignored). Here are a few examples:
const float P1 = 3.14f;    // Integer & fraction, with suffix
const float P2 = +3.1e3;   // 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
Floating-point literals must be within the range of the constant type (float or double); otherwise, the compiler will issue a diagnostic.
• String literals support the same escape sequences as C++. Here are some examples:
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
As for C++, adjacent string literals are concatenated:
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";
Note that Slice has no concept of a null string:
const string nullString = 0;    // Illegal!
Null strings simply do not exist in Slice and, therefore, do not exist as a legal value for a string anywhere in the Ice platform. The reason for this decision is that null strings do not exist in many programming languages.1

1
Many languages other than C and C++ use a byte array as the internal string representation. Null strings do not exist (and would be very difficult to map) in such languages.

Table of Contents Previous Next
Logo