These changes aren't safe. std::string::c_str() returns a const char * that's good at least until the std::string is next used. When the std::string lived in the SMDiagnostic, that was long enough to last past the end of the FFI call to the copy IO action in Haskell which read the string data. With the new SMDiagnostic, the members are StringRefs, and str() constructs a temporary std::string. You're now calling c_str() on a temporary which will be destroyed (invalidating the const char *) before the FFI call returns, leaving Haskell to read from an invalid pointer.