Open vtereshkov opened 2 years ago
As of Adept v2.6, weak ownership references do not know whether underlying data is still alive. It's up to the programmer to only use weak references if they are valid.
Weak ownership references (like weak raw pointers) should really only be used when it's obvious that they will remain valid until they (or their parent) run out of scope.
When ownership is murky, heap allocating or cloning are the two of the simplest solutions. (but also now shared pointers which I later describe in this response)
With heap allocating (raw pointers example):
import basics
func main {
original *String = new String
defer {
original.__defer__()
delete original
}
*original = "This is a string that I own".clone()
if true {
string *String = original
print("String: " + *string)
}
// We can know that 'original' still exists since it was heap allocated and
// it hasn't been explicitly destroyed yet
print("Original: " + *original)
}
With types that are cloneable (e.g. String
, <*$T> List
) for example:
import basics
import random
func main {
randomize()
original String = "This is a string that I own".clone()
copied String
if true {
string String = normalizedRandom() < 0.5 ? original.commit() : original.clone()
print("String: " + string)
copied = string.clone()
}
// We don't know if 'original' still exists or not, so it must be assumed to be invalid
// However, 'copied' is still guaranteed to be valid, so we can use that
print("Copied: " + copied)
}
Also, you can now wrap objects in shared pointers to get the same effect to what you describe. Using an implementation of shared pointers like https://github.com/IsaacShelton/AdeptSharedPtr for example,
import basics
import "Shared.adept"
func main {
name <String> WeakPtr
if true {
shared_pointer <String> SharedPtr = SharedPtr(new String)
*shared_pointer.get() = "Hello" + " " + "World"
name = shared_pointer.weak()
printf("name is %S, while data is still alive\n", *name.get())
printf("name pointer is %p, while data is still alive\n", name.get())
}
printf("name pointer is %p, when data is no longer alive\n", name.get())
}
or a more complicated example
import basics
import "Shared.adept"
record PhoneBook (people <String> List)
record Office (phonebook <PhoneBook> SharedPtr)
func main {
weak_pointer <PhoneBook> WeakPtr
if true {
office Office
if true {
phonebook <PhoneBook> SharedPtr = makeExamplePhoneBook()
office = Office(phonebook)
}
weak_pointer = office.phonebook.weak()
}
printf("%p is null, since the phonebook has no strong references remaining\n", weak_pointer.get())
}
func makeExamplePhoneBook() <PhoneBook> SharedPtr {
phonebook <PhoneBook> SharedPtr = SharedPtr(new PhoneBook)
phonebook.get().people.add("Person1")
phonebook.get().people.add("Person2")
phonebook.get().people.add("Person3")
return phonebook.clone()
}
So I suppose that using commit()
is very dangerous, donate()
being safer. But even with donate()
, I get neither a compile-time error, nor a run-time panic when trying to use a donated string. Just a "<DONATED>"
value (which may accidentally coincide with a well-formed string with the same value).
Yeah, the compiler doesn't guaranteed safety for them.
Using previously donated strings should probably be a runtime error like you describe.
You can however check the ownership status of a string though if you want to determine if it donated its contents.
import basics
func main {
// Start with two strings that have ownership
string1 String = "Hello".clone()
string2 String = "World".clone()
// Capture raw given value returned from 'donate' for one of them
given POD String = string1.donate()
defer given.__defer__()
// Capture taken value returned from 'donate' for the other one of them
taken String = string2.donate()
printf("'string1' should be marked as donated (is donor): %B\n", string1.ownership == StringOwnership::DONATED)
printf("'string2' should be marked as donated (is donor): %B\n", string2.ownership == StringOwnership::DONATED)
printf("'given' should not be marked as donated (is given) %B\n", given.ownership == StringOwnership::DONATED)
printf("'taken' should not be marked as donated (is owned) %B\n", taken.ownership == StringOwnership::DONATED)
}
'string1' should be marked as donated (is donor): true
'string2' should be marked as donated (is donor): true
'given' should not be marked as donated (is given) false
'taken' should not be marked as donated (is owned) false
Also StringOwnership::DONATED
should really be renamed to StringOwnership::DONOR
to avoid confusion, although this is a breaking change. Opt-out runtime errors for usage of donated strings is another good feature for the next release.
--- Update: ---
In the latest build of the standard library for Adept 2.7...
Runtime errors now exist when a donor string is used after donating. (opt-out by default)
And StringOwnership::DONATED
is now renamed to StringOwnership::DONOR
.
Perhaps any attempt to commit()
or donate()
something that is not owned should be a run-time error as well.
Yes donate()
should probably raise a runtime error if the subject string doesn't have ownership.
With commit()
, sometimes you only want to transfer ownership if it's possessed. So instead of breaking it, a stricter version named give()
which requires ownership, might be good to introduce.
(these changes have now been implemented in the latest version of the standard library for 2.7)
.commit()
.commit()
will transfer ownership (if able to) and will retain a reference.
(using .donate()
or .give()
is recommended instead in most cases)
pragma ignore_unused
import basics
func main {
source String = "This string has ownership".clone()
reference String = "This string does not"
// 'source' gives ownership to 'ok', but retains a reference
ok String = source.commit()
// 'reference' will give a reference to 'still_ok', since it doesn't have ownership
still_ok String = reference.commit()
}
.donate()
.donate()
will transfer ownership (or raise a runtime error if unable to). Invalidates the subject after use.
pragma ignore_unused
import basics
func main {
source String = "This string has ownership".clone()
reference String = "This string does not"
// 'source' gives ownership to 'ok', and does not retain a reference
ok String = source.donate()
// 'reference' cannot give ownership to 'failure', since it doesn't have ownership
// (this will raise a runtime error)
failure String = reference.donate()
}
.give()
.give()
will transfer ownership (or raise a runtime error if unable to). Will retain a reference.
pragma ignore_unused
import basics
func main {
source String = "This string has ownership".clone()
reference String = "This string does not"
// 'source' gives ownership to 'ok', but retains a reference
ok String = source.give()
// 'reference' cannot give ownership to 'failure', since it doesn't have ownership
// (this line will raise a runtime error)
failure String = reference.give()
}
The language documentation mentions
I understand this as that the old reference will remain valid while the new owner is alive. In other words, this old reference is now a weak reference. But how can I check that it is actually valid when I'm going to use it? How can I ensure that the new owner has not left its scope?
std::weak_ptr<T>::lock()
returnsnullptr
if the weak pointer is dangling.null
if the target is no longer valid.