suideveloper
All questions
Sui Move

How do strings work in Sui Move?

November 6, 2026

Move has no built-in string primitive. The standard library provides two wrappers around vector<u8>: std::string::String, which guarantees valid UTF-8 and is the one to use by default, and std::ascii::String, which guarantees ASCII and supports per-character operations. You create them from bytes with b"hello".to_string() (or string::utf8), and recent compilers also infer a plain "hello" literal as a String when the expected type is known. length() counts bytes, not characters.

Creating strings

Move
use std::string::{Self, String};

// From a byte string: aborts if the bytes aren't valid UTF-8.
let a: String = b"Hello".to_string();
let b: String = string::utf8(b"Hello");

// Without aborting: returns Option<String>.
let c: Option<String> = b"\xFF".try_to_string();   // none

// Typed string literal (compiler support added in late 2025).
let d: String = "Hello";
let e: std::ascii::String = "TICKER";

// Numbers to strings: every unsigned integer type has to_string().
let n: String = 42u64.to_string();

A string literal's type comes from context: it can become vector<u8>, string::String or ascii::String. If the compiler can't tell, it defaults to vector<u8> with a warning, which is why "Hello".to_string() doesn't compile. On an older toolchain without string literals, use b"...".to_string().

String has copy, drop and store, so you can put it in object fields freely.

Common operations

Move
let mut s: String = b"Hello,".to_string();
s.append(b" World!".to_string());     // "Hello, World!"
s.append_utf8(b"!");                  // append raw bytes

s.length();                           // 14 (bytes)
s.is_empty();                         // false
s.substring(0, 5);                    // "Hello"
s.index_of(&b"World".to_string());    // 7
s.insert(5, b" there".to_string());
s.as_bytes();                         // &vector<u8>
s.into_bytes();                       // vector<u8>, consumes s

index_of returns the string's length when there's no match, not an Option. Comparisons with == are byte-for-byte. sub_string and bytes still exist but are deprecated aliases for substring and as_bytes.

There's no formatting, interpolation, split or case conversion on UTF-8 strings. Build text by appending pieces.

UTF-8 pitfalls

length() returns bytes: "héllo" is 6 bytes, and an emoji is 4. substring and insert take byte indices and abort if an index lands in the middle of a multi-byte character. There's no way to index individual characters of a UTF-8 String.

ASCII strings

ascii::String fits identifiers, tickers and URLs. Every character is one byte, so it offers more operations:

Move
use std::ascii;

let t: ascii::String = b"sui".to_ascii_string();  // aborts on non-ASCII
let upper = t.to_uppercase();                     // "SUI"
let ok = t.all_characters_printable();

let utf8: String = t.to_string();   // always succeeds
let back = utf8.to_ascii();         // aborts if not ASCII

It also has push_char, pop_char, to_lowercase and index_of. The Sui framework uses ascii::String for things like a coin's symbol.

Strings in transactions

When a function takes a String or ascii::String argument, you pass a normal string from the client; Sui converts the bytes and validates the encoding. With @mysten/sui:

TypeScript
tx.moveCall({
  target: `${PACKAGE_ID}::profile::set_name`,
  arguments: [tx.object(PROFILE_ID), tx.pure.string('Alice')],
})

From the CLI, pass it as a quoted argument to sui client call. Strings stored in objects count toward object size limits and storage fees, so keep long text offchain (for example on Walrus) and store a reference.

Sources

More on sui move