ownership

Ownership is important when managing resources in C++. sol has many ownership semantics which are generally safe by default. Below are the rules.

object ownership

You can take a reference to something that exists in Lua by pulling out a sol::reference or a sol::object:

object_lifetime.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>

#include <string>
#include <iostream>

int main () {
	sol::state lua;
	lua.open_libraries(sol::lib::base);

	lua.script(R"(
	obj = "please don't let me die";
	)");

	sol::object keep_alive = lua["obj"];
	lua.script(R"(
	obj = nil;
	function say(msg)
		print(msg)
	end
	)");

	lua.collect_garbage();

	lua["say"](lua["obj"]);
	// still accessible here and still alive in Lua
	// even though the name was cleared
	std::string message = keep_alive.as<std::string>();
	std::cout << message << std::endl;

	// Can be pushed back into Lua as an argument
	// or set to a new name,
	// whatever you like!
	lua["say"](keep_alive);

	return 0;
}

All objects must be destroyed before the sol::state is destroyed, otherwise you will end up with dangling references to the Lua State and things will explode in horrible, terrible fashion.

This applies to more than just sol::object: all types derived from sol::reference and sol::object (sol::table sol::userdata, etc.) must be cleaned up before the state goes out of scope.

pointer ownership

sol will not take ownership of raw pointers: raw pointers do not own anything. sol will not delete raw pointers, because they do not (and are not supposed to) own anything:

pointer_lifetime.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>

struct my_type {
	void stuff() {
	}
};

int main() {

	sol::state lua;
	// AAAHHH BAD
	// dangling pointer!
	lua["my_func"] = []() -> my_type* { return new my_type(); };

	// AAAHHH!
	lua.set("something", new my_type());

	// AAAAAAHHH!!!
	lua["something_else"] = new my_type();
	return 0;
}

Use/return a unique_ptr or shared_ptr instead or just return a value:

(smart pointers) pointer_lifetime.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>

struct my_type {
	void stuff() {
	}
};

int main() {

	sol::state lua;
	// :ok:
	lua["my_func0"] = []() -> std::unique_ptr<my_type> { return std::make_unique<my_type>(); };

	// :ok:
	lua["my_func1"] = []() -> std::shared_ptr<my_type> { return std::make_shared<my_type>(); };

	// :ok:
	lua["my_func2"] = []() -> my_type { return my_type(); };

	// :ok:
	lua.set("something", std::unique_ptr<my_type>(new my_type()));

	std::shared_ptr<my_type> my_shared = std::make_shared<my_type>();
	// :ok:
	lua.set("something_else", my_shared);

	// :ok:
	auto my_unique = std::make_unique<my_type>();
	lua["other_thing"] = std::move(my_unique);

	return 0;
}

If you have something you know is going to last and you just want to give it to Lua as a reference, then it’s fine too:

(static) pointer_lifetime.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>

struct my_type {
	void stuff() {
	}
};

int main() {

	sol::state lua;
	lua["my_func5"] = []() -> my_type* {
		static my_type mt;
		return &mt;
	};
	return 0;
}

sol can detect nullptr, so if you happen to return it there won’t be any dangling because a sol::lua_nil will be pushed. But if you know it’s nil beforehand, please return std::nullptr_t or sol::lua_nil:

(nil/nullptr) pointer_lifetime.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>

struct my_type {
	void stuff() {
	}
};

int main() {

	sol::state lua;
	// THIS IS STILL BAD DON'T DO IT AAAHHH BAD
	// return a unique_ptr that's empty instead
	// or be explicit!
	lua["my_func6"] = []() -> my_type* { return nullptr; };

	// :ok:
	lua["my_func7"] = []() -> std::nullptr_t { return nullptr; };

	// :ok:
	lua["my_func8"] = []() -> std::unique_ptr<my_type> {
		// default-constructs as a nullptr,
		// gets pushed as nil to Lua
		return std::unique_ptr<my_type>();
		// same happens for std::shared_ptr
	};

	// Acceptable, it will set 'something' to nil
	// (and delete it on next GC if there's no more references)
	lua.set("something", nullptr);

	// Also fine
	lua["something_else"] = nullptr;

	return 0;
}

ephermeal (proxy) objects

Proxy and result types are ephermeal. They rely on the Lua stack and their constructors / destructors interact with the Lua stack. This means they are entirely unsafe to return from functions in C++, without very careful attention paid to how they are used that often requires relying on implementation-defined behaviors.

Please be careful when using (protected_)function_result, load_result (especially multiple load/function results in a single C++ function!) stack_reference, and similar stack-based things. If you want to return these things, consider