Skip to main content

13 / 16

Strings and text

Use FName for identifiers, FText for anything a player reads, and FString for manipulation.

Unreal has three string types and they are not interchangeable. Each one has a different job. The wrong choice shows up as a slow loop comparing characters, or a localization pass with nothing to translate.

Rules

  1. 01FName is an index into a global table. Comparison is an integer compare, so a map keyed by FName stays fast no matter how long the names get.
  2. 02Names are case-insensitive on comparison. Case is only preserved when WITH_CASE_PRESERVING_NAME is set, and the engine defines that as WITH_EDITORONLY_DATA, so a cooked build does not keep the original casing.
  3. 03FName cannot be edited in place, and it will not give user input back exactly as typed. Treat it as a key, not as text.
  4. 04Anything a player reads is FText. It carries the key a translator works from, which FString has no way to represent.
  5. 05Declare player-facing literals with LOCTEXT inside a LOCTEXT_NAMESPACE block, or NSLOCTEXT when the namespace has to be explicit at the call site.
  6. 06FText::FromString produces culture-invariant text. Use it for a debug label. Do not use it for anything a player sees.
  7. 07Reach for FString when the code actually builds or parses characters. Convert once at the boundary rather than repeatedly inside a loop.
  8. 08Take FStringView for a parameter that only reads an existing buffer, so the caller is not forced to allocate a copy to pass it.
  9. 09Never compare FText to drive logic. Equality and sort order on localized text change with the active culture.
  10. 10Wrap every string literal in TEXT(). Without it the literal is narrow-character and gets converted at runtime.

String types

FName

An identifier used for lookup, such as a slot, socket, tag, or data table row. Stored as a table index, so comparison and hashing cost the same at any length.

CostsImmutable and case-insensitive on comparison, so it cannot hold text a player edits or reads.

FText

Anything displayed to a player. Carries a namespace and key so a translator can supply each culture.

CostsNot usable for comparison logic, and building one at runtime needs FText::Format rather than concatenation.

FString

The code builds, parses, or edits characters. The only one of the three that is mutable.

CostsHeap allocated and copied by value, so passing it through a hot path costs more than an FName.

FStringView

A function reads an existing buffer and never stores it. Avoids the allocation an FString parameter would force on every caller.

CostsDoes not own its data, so keeping one past the call leaves a dangling view.

Match the string type to the job

An identifier, a player-facing label, and a string being assembled are three different types.

InventoryComponent.cppcpp
#define LOCTEXT_NAMESPACE "Inventory"

// Identifier. Compared by index, never shown to a player.
static const FName PrimarySlotName(TEXT("Slot.Primary"));

// Player-facing. The key is what a translator receives.
const FText PickupPrompt = LOCTEXT("PickupPrompt", "Hold E to pick up");

#undef LOCTEXT_NAMESPACE

bool UInventoryComponent::HasSlot(const FName SlotName) const
{
    // An index comparison, not a character walk.
    return EquippedSlots.Contains(SlotName);
}

FString UInventoryComponent::BuildDebugLabel(
    const FName SlotName,
    const int32 Quantity) const
{
    return FString::Printf(TEXT("%s x%d"), *SlotName.ToString(), Quantity);
}
Why
FName compares as an index, so a lookup costs the same regardless of length. FText carries the localization key that a translator needs. FString is the only one of the three built for editing characters.
Costs
FName cannot be modified and is case-insensitive on comparison, so it will not give user input back exactly as typed. FText cannot be sorted or compared without knowing the active culture.
Instead
Use FStringView when a function only reads an existing buffer and never stores it. Use FText::AsCultureInvariant when text has to bypass localization, such as a debug string.

All 16 rules