Why does cloning an Rc pointer in Rust increase its strong_count? - pointers

I have this function:
use std::rc::Rc;
fn rc_counter() -> Rc<String> {
let p1 = Rc::new(String::from("Hello"));
println!("count {}", Rc::strong_count(&p1)); // 1
// this is an owned type not a reference
let p2 = p1.clone();
println!(
"Count after the clone is created for the pointer {}",
Rc::strong_count(&p1)
); // 2
p2
}
I created p1 pointer. Print its strong count as 1
then I cloned it and checked the strong count of "p1" and it prints out 2.
As far as I know, cloning means, replicating the pointer in stack and replicating the data in the heap. But looks like cloned p2 is still related to the p1. How is this possible?

That's the whole point of Rc - Rc is a reference counting smart pointer for shared ownership. It makes it possible that multiple owners own it simultaneously.
To not break Rust's ownership rules, all of the owners can only access it immutably, though.
For that reason, .clone() is kind of discouraged, because it doesn't make it clearly readable whether it clones the Rc or the inner object.
In your case, p1.clone() is equivalent to Rc::clone(&p1).
If you want to clone the inner String, you need to do String::clone(&p1), or (*p1).clone().
As far as I know, cloning means, replicating the pointer in stack and replicating the data in the heap. But looks like cloned p2 is still related to the p1.
You are describing the behavior of Box. If you want non-shared ownership of an object on the heap, and if you clone it it also clones the object on the heap, use Box instead of Rc. The two are very similar apart of the fact that Rc is for shared ownership.

Related

returning pointer from a local variable in function

I'm reading a book called The Go Programming Language, and in the 2nd chapter about pointers the following is written
It is perfectly safe for a function to return the address of a local variable. For instance, in the
code below, the local variable v created by this particular call to f will remain in existence even
after the call has returned, and the pointer p will still refer to it:
var p = f()
func f() *int {
v := 1
return &v
}
I totally don't get this, a local variable is supposed to be destroyed after the function execution. Is it because maybe v is allocated on the heap. I know in C if you allocate space using malloc it won't be destroyed after function execution because it's on the heap.
Go isn't C. Despite similarities, it is much higher-level. It utilizes a complete runtime with a green thread scheduler and garbage-collecting memory manager. It will never collect memory so long as it has live references.
The Go compiler includes a stage called "escape analysis", where it tracks each variable to see if it "escapes" the function in which it is declared. Any value that can escape is allocated on the heap and managed by garbage collection; otherwise, it is (usually) allocated on the stack.
You can find more information on the subject:
https://blog.golang.org/ismmkeynote
https://dave.cheney.net/2014/06/07/five-things-that-make-go-fast
https://dougrichardson.org/2016/01/23/go-memory-allocations.html
https://www.agardner.me/golang/garbage/collection/gc/escape/analysis/2015/10/18/go-escape-analysis.html
https://www.ardanlabs.com/blog/2017/05/language-mechanics-on-escape-analysis.html

Common mistakes with pointers in Go [closed]

Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 5 years ago.
Improve this question
I haven't used pointers in a long time, and when I did it was only in an academic setting and I am a bit rusty now thanks to C#/Java/scala.
What are some common mistakes people make with pointers in Golang?
Are there ways of testing if you have used them correctly? I guess it is always hard to detect a memory leak until things go wrong.
Given that Go is garbage collected and doesn't allow pointer arithmetics there is not much that you can do wrong. You can use unsafe package for that but it's name speaks for itself - it's unsafe.
nil pointers are still there. Dereferencing them will cause a panic which is somewhat like exceptions in C#/Java - you get a clear error description and a stack trace where it happend.
Memory leaks - GC will do almost everything for you just like in C#/Java. But there is a special case that I know of - slices. Removing an element is usually done by creating another slice like this:
a = append(a[:i], a[i+1:]...)
this code might leak the element you removed. That's because internally slice is a struct that contains an array (just a pointer), length and capacity. When you remove an element new slice might contain the same array and it will still reference the element you removed. GC will not free it. To solve that you need to nil the element before removing it.
And there is also pointer vs value method receivers confusion. It's not a mistake, more like a design decision you have to make and understand. Method with value receiver will get a copy of the receiver, it can't modify the state. So if you want to modify the state then you need pointer receiver. Also if your structs are big and you don't want them to be copied every time you call a methid you also might want to use pointer receivers.
The common mistake I found is that people forget about pointers inside their complicated structs.
Each slice, map, interface is a pointer. When you have a struct containing another struct containing a pointer, you might just see S1{s2: S2}, and you think it's fine to have a struct copied like this: a=b, when actually it's not fine, as inside s2, a poiner vatriable, let's say p, will have their address copied, and not the value to which it points. When yoy modify the value found at *a.s2.p, the *b.s2.p will return the same value.
package main
import (
"fmt"
)
type S1 struct {
v int
s2 S2
}
type S2 struct {
p *int
}
func main() {
x := 1
b := S1{v: 10, s2: S2{p: &x}}
a := b
fmt.Printf("a = %+v\nb = %+v\n*a.s2.p = %d\n*b.s2.p = %d\na.s2.p = %p\nb.s2.p = %p\n", a, b, *a.s2.p, *b.s2.p, a.s2.p, b.s2.p)
*a.s2.p = 5
fmt.Printf("*a.s2.p = %d\n*b.s2.p = %d\na.s2.p = %p\nb.s2.p = %p\n", *a.s2.p, *b.s2.p, a.s2.p, b.s2.p)
}
http://play.golang.org/p/KQ99KICgbu
This it's a very simple example, and it looks obvious there is an issue, but on bigger applications this might not be that obvious.
This issue appears with channels as well. If you send a pointer, you will get on the other end the same pointer (a copy of your pointer's value, which is an address). In this case, as with the first case, a safe solution is to use a Clone function, to create a cloned object. You send the clone through your channel and don't use it anymore on the sender side.

cgo pointer value changed

I made bindings to a C api (bullet physics engine) using cgo, some functions make use of data pointers. The idea is that I can attach a pointer to an object and retrieve it later when the physics engine invokes a callback. My problem is that when i get the value back, it change and I didn't do it. It seems that no source code is explicitelly changing the value.
CollisionObject: source, header,
The go codes that interracts with that class
heres how i send the values, the reconversion to *int and int is fine, the correct numbers are printed:
num := x*amounty*amountz + y*amountz + z + 1
ptr := unsafe.Pointer(&num)
fmt.Printf("created %v %v\n", ptr, *(*int)(ptr))
rb := sphere.RigidBody(ptr, 1)
But when I get it back from a raytest the value changed:
ptr := hit.GetUserPointer()
log.Printf("we got back: %v %v", ptr, *(*int)(ptr))
the pointer value itself didnt change, i can look up and see that there was a pointer pointing to this location, but the value its pointing at is different.
Now i'm wondering if maybe go didn't clean the value (garbage collected) since it wouldn't be used anymore and replaced this memory location with something else.
example output (with junk values removed):
created: 0xc2080006e0 40
2014/11/07 17:10:01 we got back: 0xc2080006e0 4921947622888946315
ANY pointer (hehe) is appreciated :)
Go's garbage collector doesn't know about the pointers held by C or C++ code, so there is nothing to keep the num variable alive.
You can work around this by storing a second copy of the pointer in a Go variable. One way is to use a global variable with a type like map[*C.some_c_type]*int or similar, and store &num there too. Remember to protect the map with a mutex so things behave correctly when you have concurrent access.
In order not to leak, you will need to manually delete &num from the map when the underlying C code is no longer holding a reference to it. If the C library provides the ability to set a destroy notify function when storing the user pointer, this will be easy: just export a Go function to C and use it as the notify function. If it doesn't, but the Go binding knows when the the pointer will be finished with (e.g. if the RigidBody variable is always freed via the Go API, you can do the clean up there.

Safety of unrestricted_access

I have a complicated situation so I hope I can explain it properly.
I am using unchecked_access in combination with Ada.Finalization.Controlled types to pass references to vectors to an out of scope protected hashed map type on the Initialize procedure and removing references in the protected map on the finalize procedure. This way I thought I could assure that it would be impossible for any tasks to see out of scope references in the map.
However, the way I have the protected map organized snap shots are taken at each instance that is it used (effectively making it a normal hashed_map) which during the course of it being used vector references could go out of scope and the snap shot would still have the reference and attempt to access it.
I could see this creating 2 problems, either I am creating dangling pointers and trying to reference freed memory or my snapshot of the references is keeping the memory alive and I am leaving garbage around. I made a test to see what would happen and it seems the memory is still alive but is this really the case? Are there any other problems with my organization?
with Ada.Containers; use Ada.Containers;
with Ada.Containers.Vectors;
with Ada.Text_IO; use Ada.Text_IO;
procedure Test is
package V_Integer is new Ada.Containers.Vectors(Positive, Integer);
use V_Integer;
type V_Access is access all Vector;
Bar : V_Access;
begin
declare
Foo : aliased Vector;
begin
Bar := Foo'unrestricted_Access;
Foo.Append(3);
Foo.Append(5);
Put_Line("In scope: " & count_type'image(Length(Bar.all)));
end;
-- Will this reference always exist? Does it need to be freed?
Put_Line("Out of scope: " & count_type'image(Length(Bar.all)));
end Test;
begin
declare
Foo : aliased Vector;
begin
Bar := Foo'unrestricted_Access;
Foo.Append(3);
Foo.Append(5);
Put_Line("In scope: " & count_type'image(Length(Bar.all)));
end;
-- Will this reference always exist? Does it need to be freed?
Put_Line("Out of scope: " & count_type'image(Length(Bar.all)));
end Test;
Foo will be an object of type Vector, and it will probably exist on the stack. This object is itself a relatively small record, maybe about 6 32-bit words in GNAT (give or take a few, I haven't checked). It contains an access component that is used to get at all the vector's elements, and it contains some other housekeeping information. Bar will point to that small 6-word record. Essentially it will contain the address of something on the stack.
Since the small record Foo exists on the stack, when it goes out of scope, the stack space could be used for something else. Whether it will happen in this case, I don't know. But if you have another declare...begin...end block after the one in the example, local variables used by the new block could reuse the same stack space. Or if a procedure is called, that will put different stuff on the stack. In either case, the stack space previously used by Foo will be overwritten. And Bar will still point to the same address, but the area it points to will have been overrun with other data. So the result is likely to be wrong and could be a disaster.
I don't know if this is exactly how the implementation will work, but no matter how it's implemented, this code is potentially disastrous. The main things to keep in mind are: (1) If a variable goes out of scope, the space used for that variable may be reused at will; (2) if you use 'Unchecked_Access (or 'Unrestricted_Access) to set up a pointer to that variable, then after the variable goes out of scope, accessing data pointed to by the pointer could get you garbage or very bad behavior; (3) variables, whether aliased or not, are not (usually) dynamically allocated and the program does not need to (and cannot) explicitly free them, even if you've created a reference to them with 'Unchecked_Access.

What solutions are there for circular references?

When using reference counting, what are possible solutions/techniques to deal with circular references?
The most well-known solution is using weak references, however many articles about the subject imply that there are other methods as well, but keep repeating the weak-referencing example. Which makes me wonder, what are these other methods?
I am not asking what are alternatives to reference counting, rather what are solutions to circular references when using reference counting.
This question isn't about any specific problem/implementation/language rather a general question.
I've looked at the problem a dozen different ways over the years, and the only solution I've found that works every time is to re-architect my solution to not use a circular reference.
Edit:
Can you expand? For example, how would you deal with a parent-child relation when the child needs to know about/access the parent? – OB OB
As I said, the only good solution is to avoid such constructs unless you are using a runtime that can deal with them safely.
That said, if you must have a tree / parent-child data structure where the child knows about the parent, you're going to have to implement your own, manually called teardown sequence (i.e. external to any destructors you might implement) that starts at the root (or at the branch you want to prune) and does a depth-first search of the tree to remove references from the leaves.
It gets complex and cumbersome, so IMO the only solution is to avoid it entirely.
Here is a solution I've seen:
Add a method to each object to tell it to release its references to the other objects, say call it Teardown().
Then you have to know who 'owns' each object, and the owner of an object must call Teardown() on it when they're done with it.
If there is a circular reference, say A <-> B, and C owns A, then when C's Teardown() is called, it calls A's Teardown, which calls Teardown on B, B then releases its reference to A, A then releases its reference to B (destroying B), and then C releases its reference to A (destroying A).
I guess another method, used by garbage collectors, is "mark and sweep":
Set a flag in every object instance
Traverse the graph of every instance that's reachable, clearing that flag
Every remaining instance which still has the flag set is unreachable, even if some of those instances have circular references to each other.
I'd like to suggest a slightly different method that occured to me, I don't know if it has any official name:
Objects by themeselves don't have a reference counter. Instead, groups of one or more objects have a single reference counter for the entire group, which defines the lifetime of all the objects in the group.
In a similiar fashion, references share groups with objects, or belong to a null group.
A reference to an object affects the reference count of the (object's) group only if it's (the reference) external to the group.
If two objects form a circular reference, they should be made a part of the same group. If two groups create a circular reference, they should be united into a single group.
Bigger groups allow more reference-freedom, but objects of the group have more potential of staying alive while not needed.
Put things into a hierarchy
Having weak references is one solution. The only other solution I know of is to avoid circular owning references all together. If you have shared pointers to objects, then this means semantically that you own that object in a shared manner. If you use shared pointers only in this way, then you can hardly get cyclic references. It does not occur very often that objects own each other in a cyclic manner, instead objects are usually connected through a hierarchical tree-like structure. This is the case I'll describe next.
Dealing with trees
If you have a tree with objects having a parent-child relationship, then the child does not need an owning reference to its parent, since the parent will outlive the child anyways. Hence a non-owning raw back pointer will do. This also applies to elements pointing to a container in which they are situated. The container should, if possible, use unique pointers or values instead of shared pointers anyways, if possible.
Emulating garbage collection
If you have a bunch of objects that can wildly point to each other and you want to clean up as soon as some objects are not reachable, then you might want to build a container for them and an array of root references in order to do garbage collection manually.
Use unique pointers, raw pointers and values
In the real world I have found that the actual use cases of shared pointers are very limited and they should be avoided in favor of unique pointers, raw pointers, or -- even better -- just value types. Shared pointers are usually used when you have multiple references pointing to a shared variable. Sharing causes friction and contention and should be avoided in the first place, if possible. Unique pointers and non-owning raw pointers and/or values are much easier to reason about. However, sometimes shared pointers are needed. Shared pointers are also used in order to extend the lifetime of an object. This does usually not lead to cyclic references.
Bottom line
Use shared pointers sparingly. Prefer unique pointers and non-owning raw pointers or plain values. Shared pointers indicate shared ownership. Use them in this way. Order your objects in a hierarchy. Child objects or objects on the same level in a hierarchy should not use owning shared references to each other or to their parent, but they should use non-owning raw pointers instead.
No one has mentioned that there is a whole class of algorithms that collect cycles, not by doing mark and sweep looking for non-collectable data, but only by scanning a smaller set of possibly circular data, detecting cycles in them and collecting them without a full sweep.
To add more detail, one idea for making a set of possible nodes for scanning would be ones whose reference count was decremented but which didn't go to zero on the decrement. Only nodes to which this has happened can be the point at which a loop was cut off from the root set.
Python has a collector that does that, as does PHP.
I'm still trying to get my head around the algorithm because there are advanced versions that claim to be able to do this in parallel without stopping the program...
In any case it's not simple, it requires multiple scans, an extra set of reference counters, and decrementing elements (in the extra counter) in a "trial" to see if the self referential data ends up being collectable.
Some papers:
"Down for the Count? Getting Reference Counting Back in the Ring" Rifat Shahriyar, Stephen M. Blackburn and Daniel Frampton
http://users.cecs.anu.edu.au/~steveb/downloads/pdf/rc-ismm-2012.pdf
"A Unified Theory of Garbage Collection" by David F. Bacon, Perry Cheng and V.T. Rajan
http://www.cs.virginia.edu/~cs415/reading/bacon-garbage.pdf
There are lots more topics in reference counting such as exotic ways of reducing or getting rid of interlocked instructions in reference counting. I can think of 3 ways, 2 of which have been written up.
I have always redesigned to avoid the issue. One of the common cases where this comes up is the parent child relationship where the child needs to know about the parent. There are 2 solutions to this
Convert the parent to a service, the parent then does not know about the children and the parent dies when there are no more children or the main program drops the parent reference.
If the parent must have access to the children, then have a register method on the parent which accepts a pointer that is not reference counted, such as an object pointer, and a corresponding unregister method. The child will need to call the register and unregister method. When the parent needs to access a child then it type casts the object pointer to the reference counted interface.
When using reference counting, what are possible solutions/techniques to deal with circular references?
Three solutions:
Augment naive reference counting with a cycle detector: counts decremented to non-zero values are considered to be potential sources of cycles and the heap topology around them is searched for cycles.
Augment naive reference counting with a conventional garbage collector like mark-sweep.
Constrain the language such that its programs can only ever produce acyclic (aka unidirectional) heaps. Erlang and Mathematica do this.
Replace references with dictionary lookups and then implement your own garbage collector that can collect cycles.
i too am looking for a good solution to the circularly reference counted problem.
i was stealing borrowing an API from World of Warcraft dealing with achievements. i was implicitely translating it into interfaces when i realized i had circular references.
Note: You can replace the word achievements with orders if you don't like achievements. But who doesn't like achievements?
There's the achievement itself:
IAchievement = interface(IUnknown)
function GetName: string;
function GetDescription: string;
function GetPoints: Integer;
function GetCompleted: Boolean;
function GetCriteriaCount: Integer;
function GetCriteria(Index: Integer): IAchievementCriteria;
end;
And then there's the list of criteria of the achievement:
IAchievementCriteria = interface(IUnknown)
function GetDescription: string;
function GetCompleted: Boolean;
function GetQuantity: Integer;
function GetRequiredQuantity: Integer;
end;
All achievements register themselves with a central IAchievementController:
IAchievementController = interface
{
procedure RegisterAchievement(Achievement: IAchievement);
procedure UnregisterAchievement(Achievement: IAchievement);
}
And the controller can then be used to get a list of all the achievements:
IAchievementController = interface
{
procedure RegisterAchievement(Achievement: IAchievement);
procedure UnregisterAchievement(Achievement: IAchievement);
function GetAchievementCount(): Integer;
function GetAchievement(Index: Integer): IAchievement;
}
The idea was going to be that as something interesting happened, the system would call the IAchievementController and notify them that something interesting happend:
IAchievementController = interface
{
...
procedure Notify(eventType: Integer; gParam: TGUID; nParam: Integer);
}
And when an event happens, the controller will iterate through each child and notify them of the event through their own Notify method:
IAchievement = interface(IUnknown)
function GetName: string;
...
function GetCriteriaCount: Integer;
function GetCriteria(Index: Integer): IAchievementCriteria;
procedure Notify(eventType: Integer; gParam: TGUID; nParam: Integer);
end;
If the Achievement object decides the event is something it would be interested in it will notify its child criteria:
IAchievementCriteria = interface(IUnknown)
function GetDescription: string;
...
procedure Notify(eventType: Integer; gParam: TGUID; nParam: Integer);
end;
Up until now the dependancy graph has always been top-down:
IAchievementController --> IAchievement --> IAchievementCriteria
But what happens when the achievement's criteria have been met? The Criteria object was going to have to notify its parent `Achievement:
IAchievementController --> IAchievement --> IAchievementCriteria
^ |
| |
+----------------------+
Meaning that the Criteria will need a reference to its parent; the who are now referencing each other - memory leak.
And when an achievement is finally completed, it is going to have to notify its parent controller, so it can update views:
IAchievementController --> IAchievement --> IAchievementCriteria
^ | ^ |
| | | |
+----------------------+ +----------------------+
Now the Controller and its child Achievements circularly reference each other - more memory leaks.
i thought that perhaps the Criteria object could instead notify the Controller, removing the reference to its parent. But we still have a circular reference, it just takes longer:
IAchievementController --> IAchievement --> IAchievementCriteria
^ | |
| | |
+<---------------------+ |
| |
+-------------------------------------------------+
The World of Warcraft solution
Now the World of Warcraft api is not object-oriented friendly. But it does solve any circular references:
Do not pass references to the Controller. Have a single, global, singleton, Controller class. That way an achievement doesn't have to reference the controller, just use it.
Cons: Makes testing, and mocking, impossible - because you have to have a known global variable.
An achievement doesn't know its list of criteria. If you want the Criteria for an Achievement you ask the Controller for them:
IAchievementController = interface(IUnknown)
function GetAchievementCriteriaCount(AchievementGUID: TGUID): Integer;
function GetAchievementCriteria(Index: Integer): IAchievementCriteria;
end;
Cons: An Achievement can no longer decide to pass notifications to it's Criteria, because it doesn't have any criteria. You now have to register Criteria with the Controller
When a Criteria is completed, it notifies the Controller, who notifies the Achievement:
IAchievementController-->IAchievement IAchievementCriteria
^ |
| |
+----------------------------------------------+
Cons: Makes my head hurt.
i'm sure a Teardown method is much more desirable that re-architecting an entire system into a horribly messy API.
But, like you wonder, perhaps there's a better way.
If you need to store the cyclic data, for a snapShot into a string,
I attach a cyclic boolean, to any object that may be cyclic.
Step 1:
When parsing the data to a JSON string, I push any object.is_cyclic that hasn't been used into an array and save the index to the string. (Any used objects are replaced with the existing index).
Step 2: I traverse the array of objects, setting any children.is_cyclic to the specified index, or pushing any new objects to the array. Then parsing the array into a JSON string.
NOTE: By pushing new cyclic objects to the end of the array, will force recursion until all cyclic references are removed..
Step 3: Last I parse both JSON strings into a single String;
Here is a javascript fiddle...
https://jsfiddle.net/7uondjhe/5/
function my_json(item) {
var parse_key = 'restore_reference',
stringify_key = 'is_cyclic';
var referenced_array = [];
var json_replacer = function(key,value) {
if(typeof value == 'object' && value[stringify_key]) {
var index = referenced_array.indexOf(value);
if(index == -1) {
index = referenced_array.length;
referenced_array.push(value);
};
return {
[parse_key]: index
}
}
return value;
}
var json_reviver = function(key, value) {
if(typeof value == 'object' && value[parse_key] >= 0) {
return referenced_array[value[parse_key]];
}
return value;
}
var unflatten_recursive = function(item, level) {
if(!level) level = 1;
for(var key in item) {
if(!item.hasOwnProperty(key)) continue;
var value = item[key];
if(typeof value !== 'object') continue;
if(level < 2 || !value.hasOwnProperty(parse_key)) {
unflatten_recursive(value, level+1);
continue;
}
var index = value[parse_key];
item[key] = referenced_array[index];
}
};
var flatten_recursive = function(item, level) {
if(!level) level = 1;
for(var key in item) {
if(!item.hasOwnProperty(key)) continue;
var value = item[key];
if(typeof value !== 'object') continue;
if(level < 2 || !value[stringify_key]) {
flatten_recursive(value, level+1);
continue;
}
var index = referenced_array.indexOf(value);
if(index == -1) (item[key] = {})[parse_key] = referenced_array.push(value)-1;
else (item[key] = {})[parse_key] = index;
}
};
return {
clone: function(){
return JSON.parse(JSON.stringify(item,json_replacer),json_reviver)
},
parse: function() {
var object_of_json_strings = JSON.parse(item);
referenced_array = JSON.parse(object_of_json_strings.references);
unflatten_recursive(referenced_array);
return JSON.parse(object_of_json_strings.data,json_reviver);
},
stringify: function() {
var data = JSON.stringify(item,json_replacer);
flatten_recursive(referenced_array);
return JSON.stringify({
data: data,
references: JSON.stringify(referenced_array)
});
}
}
}
Here are some techniques described in Algorithms for Dynamic Memory Management by R. Jones and R. Lins. Both suggest that you can look at the cyclic structures as a whole in some way or another.
Friedman and Wise
The first way is one suggested by Friedman and Wise. It is for handling cyclic references when implementing recursive calls in functional programming languages. They suggest that you can observe the cycle as a single entity with a root object. To do that, you should be able to:
Create the cyclic structure all at once
Access the cyclic structure only through a single node - a root, and mark which reference closes the cycle
If you need to reuse just a part of the cyclic structure, you shouldn't add external references but instead make copies of what you need
That way you should be able to count the structure as a single entity and whenever the RC to the root falls to zero - collect it.
This should be the paper for it if anyone is interested in more details - https://www.academia.edu/49293107/Reference_counting_can_manage_the_circular_invironments_of_mutual_recursion
Bobrow's technique
Here you could have more than one reference to the cyclic structure. We just distinguish between internal and external references for it.
Overall, all allocated objects are assigned by the programmer to a group. And all groups are reference counted. We distinguish between internal and external references for the group and of course - objects can be moved between groups. In this case, intra-group cycles are easily reclaimed whenever there are no more external references to the group. However, inter-group cyclic structures should still be an issue.
If I'm not mistaken, this should be the original paper - https://dl.acm.org/doi/pdf/10.1145/357103.357104
I'm in the search for other general purpose alternatives besides those algorithms and using weak pointers.
Y Combinator
I wrote a programming language once in which every object was immutable. As such, an object could only contain pointers to objects that were created earlier. Since all reference pointed backwards in time, there could not possibly be any cyclic references, and reference counting was a perfectly viable way of managing memory.
The question then is, "How do you create self-referencing data structures without circular references?" Well, functional programming has been doing this trick forever using the fixed-point/Y combinator to write recursive functions when you can only refer to previously-written functions. See: https://en.wikipedia.org/wiki/Fixed-point_combinator
That Wikipedia page is complicated, but the concept is really not that complicated. How it works in terms of data structures is like this:
Let's say you want to make a Map<String,Object>, where the values in the map can refer to other values in the map. Well, instead of actually storing those objects, you store functions that generate those objects on-demand given a pointer to the map.
So you might say object = map.get(key), and what this will do is call a private method like _getDefinition(key) that returns this function and calls it:
Object get(String key) {
var definition = _getDefinition(key);
return definition == null ? null : definition(this);
}
So the map has a reference to the function that defines the value. This function doesn't need to have a reference to the map, because it will be passed the map as an argument.
When the definition called, it returns a new object that does have a pointer to the map in it somewhere, but the map it self does not have a pointer to this object, so there are no circular references.
There are couple of ways I know of for walking around this:
The first (and preferred one) is simply extracting the common code into third assembly, and make both references use that one
The second one is adding the reference as "File reference" (dll) instead of "Project reference"
Hope this helps

Resources