Mozilla internal string guide

Most of the Mozilla code uses a C++ class hierarchy to pass string data, rather than using raw pointers. This guide documents the string classes which are visible to code within the Mozilla codebase (code which is linked into libxul).

In a hurry? See the public API of nsAString_internal and nsACString_internal, or check out the String Quick Reference or String Rosetta Stone.

Introduction

The string classes are a library of C++ classes which are used to manage buffers of wide (16-bit) and narrow (8-bit) character strings. The headers and implementation are in the xpcom/string directory. All strings are stored as a single contiguous buffer of characters.

The 8-bit and 16-bit string classes have completely separate base classes, but share the same APIs. As a result, you cannot assign a 8-bit string to a 16-bit string without some kind of conversion helper class or routine. For the purpose of this document, we will refer to the 16-bit string classes in class documentation. Every 16-bit class has an equivalent 8-bit class:

Naming convention for wide and narrow string classes
Wide Narrow
nsAString nsACString
nsString nsCString
nsAutoString nsAutoCString
etc...

The string classes distinguish, as part of the type hierarchy, between strings that must have a null-terminator at the end of their buffer (ns[C]String) and strings that are not required to have a null-terminator (nsA[C]String). nsA[C]String is the base of the string classes (since it imposes fewer requirements) and ns[C]String is a class derived from it. Functions taking strings as parameters should generally take one of these four types.

In order to avoid unnecessary copying of string data (which can have significant performance cost), the string classes support different ownership models. All string classes support the following three ownership models dynamically:

  • reference counted, copy-on-write, buffers (the default)
  • adopted buffers (a buffer that the string class owns, but is not reference counted, because it came from somewhere else)
  • dependent buffers, that is, an underlying buffer that the string class does not own, but that the caller that constructed the string guarantees will outlive the string instance

In addition, there is a special string class, ns[C]AutoString, that additionally contains an internal 64-unit buffer (intended primarily for use on the stack), leading to a fourth ownership model:

  • storage within an auto string's stack buffer

Auto strings will prefer reference counting an existing reference-counted buffer over their stack buffer, but will otherwise use their stack buffer for anything that will fit in it.

There are a number of additional string classes, particularly nsDependent[C]String, nsDependent[C]Substring, and the NS_LITERAL_[C]STRING macros which construct an nsLiteral[C]String which exist primarily as constructors for the other types. These types are really just convenient notation for constructing an ns[C]S[ubs]tring with a non-default ownership mode; they should not be thought of as different types.

The Major String Classes

The list below describes the main base classes. Once you are familiar with them, see the appendix describing What Class to Use When.

  • nsAString/nsACString: the abstract base class for all strings. It provides an API for assignment, individual character access, basic manipulation of characters in the string, and string comparison. This class corresponds to the XPIDL AString parameter type. nsAString is not necessarily null-terminated.
  • nsString/nsCString: builds on nsAString by guaranteeing a null-terminated storage. This allows for a method (.get()) to access the underlying character buffer.

The remainder of the string classes inherit from either nsAString or nsString. Thus, every string class is compatible with nsAString.

Since every string derives from nsAString (or nsACString), they all share a simple API. Common read-only methods:

  • .Length() - the number of code units (bytes for 8-bit string classes and char16_ts for 16-bit string classes) in the string.
  • .IsEmpty() - the fastest way of determining if the string has any value. Use this instead of testing string.Length == 0
  • .Equals(string) - TRUE if the given string has the same value as the current string.

Common methods that modify the string:

  • .Assign(string) - Assigns a new value to the string.
  • .Append(string) - Appends a value to the string.
  • .Insert(string, position) - Inserts the given string before the code unit at position.
  • .Truncate(length) - shortens the string to the given length.

Complete documentation can be found in the Appendix.

Read-only strings

The const attribute on a string determines if the string is writable. If a string is defined as a const nsAString then the data in the string cannot be manipulated. If one tries to call a non-const method on a const string the compiler will flag this as an error at build time.

For example:

void nsFoo::ReverseCharacters(nsAString& str) {
      ...
     str.Assign(reversedStr); // modifies the string
}

This should not compile, because you're assigning to a const class:

void nsFoo::ReverseCharacters(const nsAString& str) {
      ...
     str.Assign(reversedStr);
}

As function parameters

For methods which are exposed across modules, use nsAString references to pass strings. For example:

// when passing a string to a method, use const nsAString&
nsFoo::PrintString(const nsAString &str);
// when getting a string from a method, use nsAString&
nsFoo::GetString(nsAString &result);

The abstract classes are also sometimes used to store temporary references to objects. You can see both of these uses in Common Patterns, below.

The Concrete Classes - which classes to use when

The concrete classes are for use in code that actually needs to store string data. The most common uses of the concrete classes are as local variables, and members in classes or structs.

nsAString_internal-graph.png

 The following is a list of the most common concrete classes. Once you are familiar with them, see the appendix describing What Class to Use When.

  • nsString / nsCString- a null-terminated string whose buffer is allocated on the heap. Destroys its buffer when the string object goes away.
  • nsAutoString / nsAutoCString- derived from nsString, a string which owns a 64 code unit buffer in the same storage space as the string itself. If a string less than 64 code units is assigned to an nsAutoString, then no extra storage will be allocated. For larger strings, a new buffer is allocated on the heap.
  • nsXPIDLString / nsXPIDLCString- derived from nsString, this class supports the getter_Copies() operator which allows easy access to XPIDL out wstring / string parameters. This class also supports the notion of a null-valued buffer, whereas nsString's buffer is never null.
  • nsDependentString- derived from nsString, this string does not own its buffer. It is useful for converting a raw string (const char16_t* or const char*) into a class of type nsAString. Note that you must null-terminate buffers used by to nsDependentString. If you don't want to or can't null-terminate the buffer, use nsDependentSubstring.
  • nsPrintfCString- derived from nsCString, this string behaves like an nsAutoCString. The constructor takes parameters which allows it to construct a 8-bit string from a printf-style format string and parameter list.
  • NS_LITERAL_STRING/NS_NAMED_LITERAL_STRING- these convert a literal string (such as "abc") to a nsString or a subclass of nsString. On platforms supporting double-byte string literals (e.g., MSVC++ or GCC with the -fshort-wchar option), these are simply macros around the nsDependentString class. They are slightly faster than just wrapping them with an nsDependentString because they use the compiler to calculate their length, and they also hide the messy cross-platform details of non-byte literal strings.

There are also a number of concrete classes that are created as a side-effect of helper routines, etc. You should avoid direct use of these classes. Let the string library create the class for you.

Of course, there are times when it is necessary to reference these string classes in your code, but as a general rule they should be avoided.

Iterators

Because Mozilla strings are always a single buffer, iteration over the characters in the string is done using raw pointers:

/**
 * Find whether there is a tab character in `data`
 */
bool HasTab(const nsAString& data)
{
  const char16_t* cur = data.BeginReading();
  const char16_t* end = data.EndReading();
  for (; cur < end; ++cur) {
    if (char16_t('\t') == *cur)
      return true;
  }
  return false;
}

Note that `end` points to the character after the end of the string buffer. It should never be dereferenced.

Writing to a mutable string is also simple:

/**
 * Replace every tab character in `data` with a space.
 */
void ReplaceTabs(nsAString& data)
{
  char16_t* cur = data.BeginWriting();
  char16_t* end = data.EndWriting();
  for (; cur < end; ++cur) {
    if (char16_t('\t') == *cur)
      *cur = char16_t(' ');
  }
}

You may change the length of a string via SetLength().  Note that Iterators become invalid after changing the length of a string:

/**
 * Replace every tab character in `data` with four spaces.
 */
void ReplaceTabs2(nsAString& data)
{
  int len = data.Length();
  char16_t *cur = data.BeginWriting();
  char16_t *end = data.EndWriting();
  // Because `cur` may change during the loop, track the position
  // within the string.
  int pos = 0;
  while (cur < end) {
    if (char16_t('\t') != *cur) {
      ++pos;
      ++cur;
    } else {
      len += 3;
      data.SetLength(len);
      // After SetLength, read `cur` and `end` again
      cur = data.BeginWriting() + pos;
      end = data.EndWriting();
      // move the remaining data over
      if (pos < len - 1)
        memmove(cur + 4, cur + 1, (len - 1 - pos) * sizeof(char16_t));
      // fill the tab with spaces
      *cur = char16_t(' ');
      *(cur + 1) = char16_t(' ');
      *(cur + 2) = char16_t(' ');
      *(cur + 3) = char16_t(' ');
      pos += 4;
      cur += 4;
    }
  }
}

If a string buffer becomes smaller while writing it, use SetLength to inform the string class of the new size:

/**
 * Remove every tab character from `data`
 */
void RemoveTabs(nsAString& data)
{
  int len = data.Length();
  char16_t* cur = data.BeginWriting();
  char16_t* end = data.EndWriting();
  while (cur < end) {
    if (char16_t('\t') == *cur) {
      len -= 1;
      end -= 1;
      if (cur < end)
        memmove(cur, cur + 1, (end - cur) * sizeof(char16_t));
    } else {
      cur += 1;
    }
  }
  data.SetLength(len);
}

Helper Classes and Functions

Searching strings - looking for substrings, characters, etc.

FindInReadable() is the replacement for the old string.Find(..). The syntax is:

PRBool FindInReadable(const nsAString& pattern,
                      nsAString::const_iterator start, nsAString::const_iterator end,
                      nsStringComparator& aComparator = nsDefaultStringComparator());

To use this, start and end should point to the beginning and end of a string that you would like to search. If the search string is found, start and end will be adjusted to point to the beginning and end of the found pattern. The return value is PR_TRUE or PR_FALSE, indicating whether or not the string was found.

An example:

const nsAString& str = GetSomeString();
nsAString::const_iterator start, end;
str.BeginReading(start);
str.EndReading(end);
NS_NAMED_LITERAL_STRING(valuePrefix, "value=");
if (FindInReadable(valuePrefix, start, end)) {
    // end now points to the character after the pattern
    valueStart = end;
}

Checking for Memory Allocation failure

The String classes now use infallible memory allocation, so you do not need to check for success when allocating/resizing "normal" strings. 

Most of the functions that modify Strings (Assign(), SetLength(), etc) also have a version that takes a "mozilla::fallible_t" parameter.  These versions return 'false' instead of aborting if allocation fails .  Use them when creating/allocating Strings which may be very large, and which the program could recover from if the allocation fails.

Getting a char * buffer from a String

You can access a String's internal buffer using the iterator methods. The String retains ownership over the buffer at all times.

In other words, there is no way to "grab" the internal char * from a String, i.e. have the string "forget" about it and hand off ownership to other code.  Sorry.

If you wish to make a copy of a String into a new character buffer (char16_t*/char*), the preferred way is to allocate it with one of the following methods:

  • char16_t* ToNewUnicode(nsAString&) - Allocates a char16_t*buffer from an nsAString.
  • char *ToNewCString(nsACString&) - Allocates a char*buffer from an nsACString. Note that this method will also work on nsAStrings, but it will do an implicit lossy conversion. This function should only be used if the input is known to be strictly ASCII. Often a conversion to UTF-8 is more appropriate. See ToNewUTF8String below.
  • char* ToNewUTF8String(nsAString&) - Allocates a new char* buffer containing the UTF-8 encoded version of the given nsAString. See Unicode Conversion for more details and for better ways that don't require you to manage the memory yourself.

These methods return a buffer allocated using XPCOM's allocator instead of the traditional allocator (malloc, etc.). Outside of libxul you should use NS_Free to deallocate the result when you no longer need it, inside libxul free() is preferred..

Substrings (string fragments)

It is very simple to refer to a substring of an existing string without actually allocating new space and copying the characters into that substring. Substring() is the preferred method to create a reference to such a string.

void ProcessString(const nsAString& str) {
    const nsAString& firstFive = Substring(str, 0, 5); // from index 0, length 5
    // firstFive is now a string representing the first 5 characters
}

Unicode Conversion ns*CString vs. ns*String

Strings can be stored in two basic formats: 8-bit code unit (byte/char) strings, or 16-bit code unit (char16_t) strings. Any string class with a capital "C" in the classname contains 8-bit bytes. These classes include nsCString, nsDependentCString, and so forth. Any string class without the "C" contains 16-bit code units.

A 8-bit string can be in one of many character encodings while a 16-bit string is always in UTF-16. The most common encodings are:

  • ASCII - 8-bit encoding for basic English-only strings. Each ASCII value is stored in exactly one byte in the array.
  • UCS2 - 16-bit encoding for a subset of Unicode, BMP. The Unicode value of a character stored in UCS2 is stored in exactly one 16-bit char16_t in a string class.
  • UTF-8 - 8-bit encoding for Unicode characters. Each Unicode characters is stored in up to 4 bytes in a string class. UTF-8 is capable of representing the entire Unicode character repertoire, and it efficiently maps to UTF-32.
  • UTF-16 - 16-bit encoding for Unicode storage, backwards compatible with UCS2. The Unicode value of a character stored in UTF-16 may require one or two 16-bit char16_ts in a string class. The contents of nsAString always has to be regarded as in this encoding instead of UCS2. UTF-16 is capable of representing the entire Unicode character repertoire, and it efficiently maps to UTF-32. (Win32 W APIs and Mac OS X natively use UTF-16.)

In addition, there are literally hundreds of encodings that are provided by internationalization libraries. Access to these libraries may be part of the application (such as nsICharsetConversionManager in Mozilla) or built into the operating system (such as iconv() in UNIX operating systems and MultiByteToWideChar/WideCharToMultiByte on Windows).

When working with existing code, it is important to examine the current usage of the strings that you are manipulating, to determine the correct conversion mechanism.

When writing new code, it can be confusing to know which storage class and encoding is the most appropriate. There is no single answer to this question, but there are a few important guidelines:

  • Is the string always ASCII? First and foremost, you need to determine what kinds of values will be stored in the string. If the strings are always internal, ASCII strings such as "left", "true", "background" and so forth, then straight C-strings are probably the way to go.
  • If the string is ASCII, will it be compared to, assigned to, or otherwise interact with non-ASCII strings? When assigning or comparing an 8-bit ASCII value (in)to a 16-bit UCS2 string, an "inflation" needs to happen at runtime. If your strings are small enough (say, less than 64 bytes) then it may make sense to store your string in a 16-bit unicode class as well, to avoid the extra conversion. The tradeoff is that your ASCII string takes up twice as much space as a 16-bit Unicode string than it would as an 8-bit string.
  • Is the string usually ASCII, but needs to support unicode? If your string is most often ASCII but needs to be able to store Unicode characters, then UTF-8 may be the right encoding. ASCII characters will still be stored in 8-bit storage but other Unicode characters will take up 2 to 4 bytes. However if the string ever needs to be compared or assigned to a 16-bit string, a runtime conversion will be necessary.
  • Are you storing large strings of non-ASCII data? Up until this point, UTF-8 might seem like the ideal encoding. The drawback is that for most non-European characters (such as Chinese, Indian and Japanese) in BMP, UTF-8 takes 50% more space than UTF-16. For characters in plane 1 and above, both UTF-8 and UTF-16 take 4 bytes.
  • Do you need to manipulate the contents of a Unicode string? One problem with encoding Unicode characters in UTF-8 or other 8-bit storage formats is that the actual Unicode character can span multiple bytes in a string. In most encodings, the actual number of bytes varies from character to character. When you need to iterate over each character, you must take the encoding into account. This is vastly simplified when iterating 16-bit strings because each 16-bit code unit (char16_t) corresponds to a Unicode character as long as all characters are in BMP, which is often the case. However, you have to keep in mind that a single Unicode character in plane 1 and beyond is represented in two 16-bit code units in 16-bit strings so that the number of char16_t's is not always equal to the number of Unicode characters. For the same reason, the position and the index in terms of 16-bit code units are not always the same as the position and the index in terms of Unicode characters.


To assist with ASCII, UTF-8, and UTF-16 conversions, there are some helper methods and classes. Some of these classes look like functions, because they are most often used as temporary objects on the stack.

UTF-8 / UTF-16 conversion

NS_ConvertUTF8toUTF16(const nsACString&) - a nsAutoString subclass that converts a UTF-8 encoded nsACString or const char* to a 16-bit UTF-16 string. If you need a const char16_t* buffer, you can use the .get() method. For example:

/* signature: void HandleUnicodeString(const nsAString& str); */
object->HandleUnicodeString(NS_ConvertUTF8toUTF16(utf8String));
/* signature: void HandleUnicodeBuffer(const char16_t* str); */
object->HandleUnicodeBuffer(NS_ConvertUTF8toUTF16(utf8String).get());

NS_ConvertUTF16toUTF8(const nsAString&) - a nsAutoCString which converts a 16-bit UTF-16 string (nsAString) to a UTF-8 encoded string. As above, you can use .get() to access a const char* buffer.

/* signature: void HandleUTF8String(const nsACString& str); */
object->HandleUTF8String(NS_ConvertUTF16toUTF8(utf16String));
/* signature: void HandleUTF8Buffer(const char* str); */
object->HandleUTF8Buffer(NS_ConvertUTF16toUTF8(utf16String).get());

CopyUTF8toUTF16(const nsACString&, nsAString&) - converts and copies:

// return a UTF-16 value
void Foo::GetUnicodeValue(nsAString& result) {
    CopyUTF8toUTF16(mLocalUTF8Value, result);
 }

AppendUTF8toUTF16(const nsACString&, nsAString&) - converts and appends:

// return a UTF-16 value
void Foo::GetUnicodeValue(nsAString& result) {
    result.AssignLiteral("prefix:");
    AppendUTF8toUTF16(mLocalUTF8Value, result);
}


UTF8ToNewUnicode(const nsACString&, PRUint32* aUTF16Count = nsnull) - allocates and converts (the optional parameter will contain the number of 16-byte units upon return, if non-null):

void Foo::GetUTF16Value(char16_t** result) {
    *result = UTF8ToNewUnicode(mLocalUTF8Value);
}


CopyUTF16toUTF8(const nsAString&, nsACString&) - converts and copies:

// return a UTF-8 value
void Foo::GetUTF8Value(nsACString& result) {
    CopyUTF16toUTF8(mLocalUTF16Value, result);
}

AppendUTF16toUTF8(const nsAString&, nsACString&) - converts and appends:

// return a UTF-8 value
void Foo::GetUnicodeValue(nsACString& result) {
    result.AssignLiteral("prefix:");
    AppendUTF16toUTF8(mLocalUTF16Value, result);
}

ToNewUTF8String(const nsAString&) - allocates and converts:

void Foo::GetUTF8Value(char** result) {
    *result = ToNewUTF8String(mLocalUTF16Value);
}

Lossy Conversion

The following should only be used when you can guarantee that the original string is ASCII. These helpers are very similar to the UTF-8 / UTF-16 conversion helpers above.

UTF-16 to ASCII converters

These converters are very dangerous because they lose information during the conversion process. You should avoid UTF-16 to ASCII conversions unless your strings are guaranteed to be ASCII. Each 16-bit code unit in 16-bit string is simply cast to an 8-bit byte, which means all Unicode character values above 0xFF are converted to an arbitrary 8-bit byte.

  • NS_LossyConvertUTF16toASCII(nsAString) - a nsAutoCString which holds a temporary buffer containing the deflated value of the string.
  • LossyCopyUTF16toASCII(nsAString, nsACString) - does an in-place conversion from UTF-16 into an ASCII string object.
  • LossyAppendUTF16toASCII(nsAString, nsACString) - appends an UTF-16 string to an ASCII string, losing non-ASCII values.
  • ToNewCString(nsAString) - allocates a new char* string.

ASCII to UTF-16 converters

These converters are very dangerous because they will mangle any non-ASCII string into a meaningless UTF-16 string. You should avoid ASCII to UTF-16 conversions unless your strings are guaranteed to be ASCII. For instance, if you have an 8-bit string encoded in a multibyte character encoding, each byte of the string will be "inflated" to a 16-bit number by simple casting.

For example, imagine a UTF-8 string where the first Unicode character of the string is represented with a 3-byte UTF-8 sequence, the "inflated" UTF-16 string will contain the 3 char16_t's instead of the single char16_t that represents the first character. These char16_t's have nothing to do with the first Unicode character in the UTF-8 string.

  • NS_ConvertASCIItoUTF16(nsACString) - a nsAutoString which holds a temporary buffer containing the inflated value of the string.
  • CopyASCIItoUTF16(nsACString, nsAString) - does an in-place conversion from one string into a Unicode string object.
  • AppendASCIItoUTF16(nsACString, nsAString) - appends an ASCII string to a Unicode string.
  • ToNewUnicode(nsACString) - Creates a new char16_t* string which contains the inflated value.

Comparing ns*Strings with C strings

You can compare ns*Strings with C strings by converting the ns*String to a C string, or by comparing directly against a C String.

  • PromiseFlatCString(nsACString).get() - creates a temporary char * out of a nsACString. This can be compared to a C String using C functions.
  • ns*String.EqualsASCII(const char *) - compares with an ascii C string.
  • ns*String.EqualsLiteral - compares with a string literal.

Common Patterns

Callee-allocated Parameters

Many APIs result in a method allocating a buffer in order to return strings to its caller. This can be tricky because the caller has to remember to free the string when they have finished using it. Fortunately, the nsXPIDLString class makes this very easy.

A method may look like this:

void GetValue(char16_t** aValue)
{
    *aValue = ToNewUnicode(foo);
}

Without the string classes, the caller would need to free the string:

{
    char16_t* val;
    GetValue(&val);
    if (someCondition) {
        // don't forget to free the value!
        NS_Free(val);
        return NS_ERROR_FAILURE;
    }
    ...
    // and later, still don't forget to free!
    NS_Free(val);
}

With nsXPIDLString you never have to worry about this. You can just use getter_Copies() to wrap the string class, and the class will remember to free the buffer when it goes out of scope:

{
    nsXPIDLString val;
    GetValue(getter_Copies(val));
    // val will free itself here
    if (someCondition)
        return NS_ERROR_FAILURE;
    ...
    // and later, still nothing to free
}

The resulting code is much simpler, and easy to read.

Literal Strings

A literal string is a raw string value that is written in some C++ code. For example, in the statement printf("Hello World\n"); the value "Hello World\n" is a literal string. It is often necessary to insert literal string values when an nsAString or nsACString is required. These four macros will provide you with the necessary conversion:

  • NS_LITERAL_CSTRING(literal string) - a temporary nsCString
  • NS_NAMED_LITERAL_CSTRING(variable,literal string) - declares a nsCString variable named variable
  • NS_LITERAL_STRING(literal string) - a temporary nsString with the unicode version of literal string
  • NS_NAMED_LITERAL_STRING(variable,literal string) - declares a nsString variable named variable with the unicode version of literal string

The purpose of the CSTRING versions of these macros may seem unnecessary, given that nsDependentCString will also wrap a string value in an nsCString. The advantage to these macros is that the length of these strings is calculated at compile time, so the string does not need to be scanned at runtime to determine its length.

The STRING versions of these macros provide a portable way of declaring UTF-16 versions of the given literal string, avoiding runtime conversion on platforms which support literal UTF-16 strings (e.g., MSVC++ and GCC with the -fshort-wchar option).

// call Init(const char16_t*)
Init(L"start value"); // bad - L"..." is not portable!
Init(NS_ConvertASCIItoUTF16("start value").get()); // bad - runtime ASCII->UTF-16 conversion!
// call Init(const nsAString&)
Init(nsDependentString(L"start value")); // bad - not portable!
Init(NS_ConvertASCIItoUTF16("start value")); // bad - runtime ASCII->UTF-16 conversion!
// call Init(const nsACString&)
Init(nsDependentCString("start value")); // bad - length determined at runtime

Here are some examples of proper NS_LITERAL_[C]STRING usage.

// call Init(const char16_t*)
Init(NS_LITERAL_STRING("start value").get());
// call Init(const nsAString&)
Init(NS_LITERAL_STRING("start value"));
// call Init(const nsACString&)
Init(NS_LITERAL_CSTRING("start value"));

There are a few details which can be useful in tracking down issues with these macros:

NS_LITERAL_STRING does compile-time conversion to UTF-16 on some platforms (e.g. Windows, Linux, and Mac) but does runtime conversion on other platforms. By using NS_LITERAL_STRING your code is guaranteed to use the best possible conversion for the platform in question.

Because some platforms do runtime conversion, the use of literal string concatenation inside a NS_LITERAL_STRING/NS_NAMED_LITERAL_STRING macro will compile on these platforms, but not on platforms which support compile-time conversion.

For example:

// call Init(nsAString&)
Init(NS_LITERAL_STRING("start "
     "value")); // only compiles on some platforms

The reason for this is that on some platforms, the L"..." syntax is used, but it is only applied to the first string in the concatenation ("start "). When the compiler attempts to concatenate this with the non-Unicode string "value" it gets confused.

Also, using preprocessor macros as the string literal is unsupported:

#define some_string "See Mozilla Run"
...
Init(NS_LITERAL_STRING( some_string )); // only compiles on some platforms/with some compilers.

String Concatenation

Strings can be concatenated together using the + operator. The resulting string is a const nsSubstringTuple object. The resulting object can be treated and referenced similarly to a nsAString object. Concatenation does not copy the substrings. The strings are only copied when the concatenation is assigned into another string object. The nsSubstringTuple object holds pointers to the original strings. Therefore, the nsSubstringTuple object is dependent on all of its substrings, meaning that their lifetime must be at least as long as the nsSubstringTuple object.

For example, you can use the value of two strings and pass their concatenation on to another function which takes an const nsAString&:

void HandleTwoStrings(const nsAString& one, const nsAString& two) {
    // call HandleString(const nsAString&)
    HandleString(one + two);
}

NOTE: The two strings are implicitly combined into a temporary nsString in this case, and the temporary string is passed into HandleString. If HandleString assigns its input into another nsString, then the string buffer will be shared in this case negating the cost of the intermediate temporary. You can concatenate N strings and store the result in a temporary variable:

NS_NAMED_LITERAL_STRING(start, "start ");
NS_NAMED_LITERAL_STRING(middle, "middle ");
NS_NAMED_LITERAL_STRING(end, "end");
// create a string with 3 dependent fragments - no copying involved!
nsString combinedString = start + middle + end;
// call void HandleString(const nsAString&);
HandleString(combinedString);

If you are using NS_LITERAL_STRING to create a temporary that is only used once, then it is safe to define it inside a concatenation because the string buffer will live as long as the temporary concatenation object (of type nsSubstringTuple).

// call HandlePage(const nsAString&);
// safe because the concatenated-string will live as long as its substrings
HandlePage(NS_LITERAL_STRING("start ") + NS_LITERAL_STRING("end"));

Local variables

Local variables within a function are usually stored on the stack. The nsAutoString/nsAutoCString classes are derivatives of the nsString/nsCString classes. They own a 64-character buffer allocated in the same storage space as the string itself. If the nsAutoString is allocated on the stack, then it has at its disposal a 64-character stack buffer. This allows the implementation to avoid allocating extra memory when dealing with small strings.

...
nsAutoString value;
GetValue(value); // if the result is less than 64 code units,
                 // then this just saved us an allocation
...

Member variables

In general, you should use the concrete classes nsString and nsCString for member variables.

class Foo {
    ...
    // these store UTF-8 and UTF-16 values respectively
    nsCString mLocalName;
    nsString mTitle;
};

Note that the strings are declared directly in the class, not as pointers to strings. Don't do this:

class Foo {
public:
    Foo() { 
        mLocalName = new nsCString(); 
        mTitle = new nsString(); 
    }
    ~Foo() { delete mLocalName; delete mTitle; }
private:
    // these store UTF-8 and UTF-16 values respectively
    nsCString* mLocalName;
    nsString*  mTitle;
};

The above code may appear to save the cost of the string objects, but nsString/nsCString are small objects - the overhead of the allocation outweighs the few bytes you'd save by keeping a pointer.

Another common incorrect pattern is to use nsAutoString/nsAutoCString for member variables. As described in Local Variables, these classes have a built in buffer that make them very large. This means that if you include them in a class, they bloat the class by 64 bytes (nsAutoCString) or 128 bytes (nsAutoString).

An example:

class Foo {
    ...
    // bloats 'Foo' by 128 bytes!
    nsAutoString mLocalName;
};

Raw Character Pointers

PromiseFlatString() and PromiseFlatCString() can be used to create a temporary buffer which holds a null-terminated buffer containing the same value as the source string. PromiseFlatString() will create a temporary buffer if necessary. This is most often used in order to pass an nsAString to an API which requires a null-terminated string.

In the following example, an nsAString is combined with a literal string, and the result is passed to an API which requires a simple character buffer.

// Modify the URL and pass to AddPage(const char16_t* url)
void AddModifiedPage(const nsAString& url) {
    NS_NAMED_LITERAL_STRING(httpPrefix, "http://");
    const nsAString& modifiedURL = httpPrefix + url;
    // creates a temporary buffer
    AddPage(PromiseFlatString(modifiedURL).get());
}

PromiseFlatString() is smart when handed a string that is already null-terminated. It avoids creating the temporary buffer in such cases.

// Modify the URL and pass to AddPage(const char16_t* url)
void AddModifiedPage(const nsAString& url, PRBool addPrefix) {
    if (addPrefix) {
        // MUST create a temporary buffer - string is multi-fragmented
        NS_NAMED_LITERAL_STRING(httpPrefix, "http://");
        AddPage(PromiseFlatString(httpPrefix + modifiedURL));
    } else {
        // MIGHT create a temporary buffer, does a runtime check
        AddPage(PromiseFlatString(url).get());
    }
}

printf and a UTF-16 string

For debugging, it's useful to printf a UTF-16 string (nsString, nsAutoString, nsXPIDLString, etc). To do this usually requires converting it to an 8-bit string, because that's what printf expects. However, on Windows, the following should work:

printf("%S\n", yourString.get());

(Note: I didn't test this. Also, I'm not sure what exactly this does to non-ASCII characters, especially when they are outside the system codepage). The reason that this doesn't work on Unix is because a wchar_t, which is what %S expects, is usually 4 bytes there (even when Mozilla is compiled with -fshort-wchar, because this would require libc to be compiled with -fshort-wchar).

If non-ASCII characters aren't important, use:

printf("%s\n", NS_LossyConvertUTF16toASCII(yourString).get());

On platforms that use UTF-8 for console output (most Linux distributions), this works:

printf("%s\n", NS_ConvertUTF16toUTF8(yourString).get());

IDL

The string library is also available through IDL. By declaring attributes and methods using the specially defined IDL types, string classes are used as parameters to the corresponding methods.

IDL String types

The C++ signatures follow the abstract-type convention described above, such that all method parameters are based on the abstract classes. The following table describes the purpose of each string type in IDL.

IDL type C++ Type Purpose
string char* Raw character pointer to ASCII (7-bit) string, no string classes used. High bit is not guaranteed across XPConnect boundaries.
wstring char16_t* Raw character pointer to UTF-16 string, no string classes used.
AString nsAString UTF-16 string.
ACString nsACString 8-bit string. All bits are preserved across XPConnect boundaries.
AUTF8String nsACString UTF-8 string. Converted to UTF-16 as necessary when value is used across XPConnect boundaries.
DOMString nsAString UTF-16 string type used in the DOM. The same as AString with a few odd XPConnect exceptions: When the special JavaScript value null is passed to a DOMString parameter of an XPCOM method, it becomes a void DOMString. The special JavaScript value undefined becomes the string "undefined".

C++ Signatures

In IDL, in parameters are read-only, and the C++ signatures for *String parameters follows the above guidelines by using const nsAString& for these parameters. out and inout parameters are defined simply as nsAString so that the callee can write to them.

IDL C++
interface nsIFoo : nsISupports {
    attribute AString utf16String;
    AUTF8String getValue(in ACString key);
};
class nsIFoo : public nsISupports {
     NS_IMETHOD GetUtf16String(nsAString&
                               aResult) = 0;
     NS_IMETHOD SetUtf16String(const nsAString&
                              aValue) = 0;
     NS_IMETHOD GetValue(const nsACString& aKey,
                     nsACString& aResult) = 0;
};

In the above example, utf16String is treated as a UTF-16 string. The implementation of GetUtf16String() will use aResult.Assign to "return" the value. In SetUtf16String() the value of the string can be used through a variety of methods including Iterators, PromiseFlatString, and assignment to other strings.

In GetValue(), the first parameter, aKey, is treated as a raw sequence of 8-bit values. Any non-ASCII characters in aKey will be preserved when crossing XPConnect boundaries. The implementation of GetValue() will assign a UTF-8 encoded 8-bit string into aResult. If the this method is called across XPConnect boundaries, such as from a script, then the result will be decoded from UTF-8 into UTF-16 and used as a Unicode value.

Choosing a string type

It can be difficult to determine the correct string type to use for IDL. The following points should help determine the appropriate string type.

  • Using string classes may avoid new memory allocation for out parameters. For example, if the caller is using an nsAutoString to receive the value for an out parameter, (defined in C++ as simply nsAString& then assignment of short (less than 64-characters) values to an out parameter will only copy the value into the nsAutoString's buffer. Moreover, using the string classes allows for sharing of string buffers. In many cases, assigning from one string object to another avoids copying in favor of simply incrementing a reference count.
  • in strings using string classes often have their length pre-calculated. This can be a performance win.
  • In cases where a raw-character buffer is required, string and wstring provide faster access than PromiseFlatString.
  • UTF-8 strings defined with AUTF8String may need to be decoded when crossing XPConnect boundaries. This can be a performance hit. On the other hand, UTF-8 strings take up less space for strings that are commonly ASCII.
  • UTF-16 strings defined with wstring or AString are fast when the unicode value is required. However, if the value is more often ASCII, then half of the storage space of the underlying string may be wasted.

String Guidelines

Follow these simple rules in your code to keep your fellow developers, reviewers, and users happy.

Appendix A - What class to use when

This table provides a quick reference for what classes you should be using.

Context class Notes
Local Variables nsAutoString
nsAutoCString
 
Class Member Variables nsString
nsCString
 
Method Parameter types nsAString
nsACString
Use abstract classes for parameters. Use const nsAString& for "in" parameters and nsAString& for "out" parameters.
Retrieving "out" string/wstrings nsXPIDLString
nsXPIDLCString
Use getter_Copies(). Similar to nsString / nsCString.
Wrapping character buffers nsDependentString
nsDependentCString
Wrap const char* / const char16_t* buffers.
Literal strings NS_LITERAL_STRING
NS_LITERAL_CSTRING
Similar to nsDependent[C]String, but pre-calculates length at build time.

Appendix B - nsAString Reference

Read-only methods.

  • Length()
  • IsEmpty()
  • IsVoid() - XPConnect will convert void nsAStrings to JavaScript null.
  • BeginReading(iterator)
  • EndReading(iterator)
  • Equals(string[, comparator])
  • First()
  • Last()
  • CountChar()
  • Left(outstring, length)
  • Mid(outstring, position, length)
  • Right(outstring, length)
  • FindChar(character)

Methods that modify the string.

  • Assign(string)
  • Append(string)
  • Insert(string)
  • Cut(start, length)
  • Replace(start, length, string)
  • Truncate(length)
  • SetIsVoid(true) - Make it null. XPConnect will convert void nsAStrings to JavaScript null.
  • BeginWriting(iterator)
  • EndWriting(iterator)
  • SetCapacity()

Original Document Information

  • Author: Alec Flett
  • Copyright Information: Portions of this content are © 1998–2007 by individual mozilla.org contributors; content available under a Creative Commons license | Details.
  • Thanks to David Baron for actual docs,
  • Peter Annema for lots of direction
  • Myk Melez for some more docs
  • David Bradley for a diagram
  • Revised by Darin Fisher for Mozilla 1.7
  • Revised by Jungshik Shin to clarify character encoding issues