How to better use pointers?

I love pointers, but there are limited functions in julia.

C/C++ Julia
p=&a p=pointer_from_objref(a)
a=*p a=unsafe_pointer_to_objref(p)
*p=3 how?
p->x=3 how?
p->tab["a"]=3 how?

etc.

Don’t. These are intentionally unappealing and hard to use. Pointer programming is unsafe and will lead to crashes unless done very carefully. There is no need to use pointers in Julia unless you are interfacing with C libraries.

6 Likes

pointers are unsafe, but they are expressive
for example, it’s hard to specify a node on a tree (structure like Expr) without pointers

Here’s a simple binary tree data structure:

mutable struct Tree{T}
    data  :: T
    left  :: Union{Tree{T}, Nothing}
    right :: Union{Tree{T}, Nothing}
end

Tree(x) = Tree{typeof(x)}(x, nothing, nothing)

Here it is in action:

julia> t = Tree("Hello")
Tree{String}("Hello", nothing, nothing)

julia> t.left = Tree("Left child")
Tree{String}("Left child", nothing, nothing)

julia> t.right = Tree("Right child")
Tree{String}("Right child", nothing, nothing)

julia> t
Tree{String}("Hello", Tree{String}("Left child", nothing, nothing), Tree{String}("Right child", nothing, nothing))

There are several real-world tree implementations in DataStructures. None of them use pointers.

2 Likes

I think you didn’t get my point. It is easy to build the structure.
But what if you want to find (the l-l-l-r-r-l or sth )child and does operations.
e.g.
This is my simple code of building a simple filesystem using pointers.

#include <iostream>
#include <map>
#include <string>
#include <vector>
using std::cin;
using std::cout;
using std::getline;
using std::map;
using std::out_of_range;
using std::string;
using std::vector;
vector<string> split(string s,char spilter){
    vector<string>t;
    string temp;
    for(char i:s){
        if(i==spilter&&temp!=""){
            t.push_back(temp);
            temp="";
        }
        else{
            temp.push_back(i);
        }
    }
    if(temp!="")t.push_back(temp);
    return t;
}
enum FileSystemError{
    VOID_STR,
    UNFOUND,DIR_UNFOUND,FILE_UNFOUND,
    SAME_NAME_EXISTS,
    PROCESS_DENIED,
};

class File;
class Directory;
class FileSystem;

class File{
public:
    Directory *in;
    string filename;
    string contents;
    File(Directory*,string,string);
    File(File&);
    ~File();
};

class Directory
{
public:
    Directory *in;
    map<string,Directory*>dirs;
    map<string,File*>files;
    string dirname;
    Directory(Directory*,string);
    Directory(Directory&);
    ~Directory();

    bool hasdir(string);
    bool hasfile(string);
    bool has(string);
};
class FileSystem
{
public:
    Directory *root_dir;
    Directory *current_dir;
    FileSystem();
    ~FileSystem();
    Directory* getdir(Directory*,vector<string>);
    Directory* getdir(string);
    Directory* getdirin(string,string&);
    File* getfile(string);
    void ls_a(Directory*,unsigned);

    void ls(bool);
    void cd(string);
    void pwd();
    void mkdir(string);
    void rmdir(string);
    void cp(string,string);
    void rm(string);
    void mv(string,string);
    void touch(string,string);
    void cat(string);
};
File::File(Directory*p,string name,string con){
    in=p;filename=name;contents=con;
}
File::File(File&a){
    in=nullptr;
    filename=a.filename;
    contents=a.contents;
}
File::~File(){if(in!=nullptr){in->files.erase(filename);}}
Directory::Directory(Directory*place,string name){in=place;dirname=name;}
Directory::Directory(Directory&a){
    in=nullptr;
    dirname=a.dirname;
    for(auto i:a.dirs){
        Directory* p=i.second;
        dirs[i.first]=p;
        p->in=this;
    }
    for(auto i:a.files){
        File *p=i.second;
        files[i.first]=p;
        p->in=this;
    }
}
Directory::~Directory(){
    if(in!=nullptr)in->dirs.erase(dirname);
    for(auto i:dirs)delete i.second;
    for(auto i:files)delete i.second;
}
bool Directory::hasdir(string name){
    return dirs.find(name)!=dirs.end();
}
bool Directory::hasfile(string name){
    return files.find(name)!=files.end();
}
bool Directory::has(string name){
    return hasdir(name)||hasfile(name);
}
FileSystem::FileSystem(){
    root_dir=new Directory(nullptr,"");
    current_dir=root_dir;
}
FileSystem::~FileSystem(){delete root_dir;}
Directory *FileSystem::getdir(Directory*p,vector<string>path){
    for(auto i:path){
        if(i==".."){
            if(p!=nullptr)p=p->in;
        }
        else if(i!="."){
            if(!p->hasdir(i))throw DIR_UNFOUND;
            p=p->dirs[i];
        }
    }
    return p;
}
Directory *FileSystem::getdir(string path){
    if(path=="")throw VOID_STR;
    Directory* p=(path[0]=='/') ? root_dir : current_dir;
    return getdir(p,split(path,'/'));
}
Directory *FileSystem::getdirin(string path,string&back){
    if(path=="")throw VOID_STR;
    Directory* p=(path[0]=='/') ? root_dir : current_dir;
    auto d=split(path,'/');
    back=d.back();
    d.pop_back();
    return getdir(p,d);
}
File *FileSystem::getfile(string path){
    string name;
    auto p=getdirin(path,name);
    if(p->hasfile(name))return p->files[name];
    else throw FILE_UNFOUND;
}
void FileSystem::ls_a(Directory* pl,unsigned step){
    for(auto i:pl->dirs){
        for(unsigned i=0;i<step;++i)cout<<'\t';
        cout<<i.first<<"/\n";
        ls_a(i.second,step+1);
    }
    for(auto i:pl->files){
        for(unsigned i=0;i<step;++i)cout<<'\t';
        cout<<i.first<<"\n";
    }
}
void FileSystem::ls(bool _a){
    if(_a){
        ls_a(current_dir,0);
    }
    else{
        for(auto i:current_dir->dirs)cout<<i.first<<"/\n";
        for(auto i:current_dir->files)cout<<i.first<<"\n";
    }
}
void FileSystem::cd(string path){
    current_dir=getdir(path);
}
void FileSystem::pwd(){
    string s;
    Directory *temp=current_dir;
    while(temp!=root_dir){
        s=temp->dirname+"/"+s;
        temp=temp->in;
    }
    cout<<"/"+s<<'\n';
}
void FileSystem::mkdir(string path){
    string name;
    auto p=getdirin(path,name);
    p->dirs[name]=new Directory(p,name);
}
void FileSystem::rmdir(string path){
    auto p=getdir(path);
    auto q=current_dir;
    while(q!=nullptr){
        if(q==p)throw PROCESS_DENIED;
        q=current_dir->in;
    }
    delete p;
}
void FileSystem::cp(string from,string to){
    string name;
    auto p=getdirin(from,name);
    auto q=getdir(to);
    if(q->has(name))throw SAME_NAME_EXISTS;
    if(p->hasdir(name)){
        auto t=*(p->dirs[name]);
        q->dirs[name]=&t;
    }else if(p->hasfile(name)){
        auto t=*(p->files[name]);
        q->files[name]=&t;
    }
    else throw UNFOUND;
}
void FileSystem::rm(string path){
    string name;
    auto p=getdirin(path,name);
    if(p->hasdir(name)){
        delete p->dirs[name];
        p->dirs.erase(name);
    }
    else if(p->hasfile(name)){
        delete p->files[name];
        p->files.erase(name);
    }
    else throw UNFOUND;
}
void FileSystem::mv(string from,string to){
    string name;
    auto p=getdirin(from,name);
    auto q=getdir(to);
    if(p==q)return;
    if(q->has(name))throw SAME_NAME_EXISTS;
    if(p->hasdir(name)){
        auto t=*(p->dirs[name]);
        q->dirs[name]=&t;
        delete p->dirs[name];
        p->dirs.erase(name);
    }else if(p->hasfile(name)){
        auto t=*(p->files[name]);
        q->files[name]=&t;
        delete p->files[name];
        p->files.erase(name);
    }
    else throw UNFOUND;
}
void FileSystem::touch(string path,string contents=""){
    string name;
    auto p=getdirin(path,name);
    if(p->hasdir(name))throw SAME_NAME_EXISTS;
    else if(p->hasfile(name)){
        p->files[name]->contents=contents;
    }
    else{
        p->files[name]=new File(p,name,contents);
    }
}
void FileSystem::cat(string path){
    auto p=getfile(path);
    cout<<p->contents<<"\n";
}

without pointers, we can only use a list and will be quite slow

Everything you do with pointers in that code you can do with normal object references in Julia. Are you talking about emulating the Directory class? You would implement that as something like this:

mutable struct Directory
    in :: Directory
    dirs :: Dict{String, Directory}
    files :: Dict{String, File}
    dirname :: String
end
2 Likes

But how do you do this Directory *current_dir;?

By the way, I’m asking about how to use instead of why to use.

current_dir = # some Directory object

You’ll have to be more specific about what you want to do in order for me to be more helpful…

The FileSystem type would be declared as:

mutable struct FileSystem
    root_dir :: Directory
    current_dir :: Directory
end

Those fields refer to Directory objects and work similar to pointers (except that you can’t segfault your program with them).

2 Likes

If you are worried about chaining the accesses to traverse the tree,
you can just write: (Here without an actual tree and mathematical functions instead)

julia> left(x) = 2x
left (generic function with 1 method)

julia> right(x) = 2x+1
right (generic function with 1 method)

julia> ∘(left, left, right, left)(1)
20
2 Likes

I already know about that and that only works for binary trees.

for (left, left, right, left), it will be slow if long.

The point is, current_dir is in root_dir! They are related!

Clearly I’m not getting it. What specific part of the code requires pointers? The C++ code is only using pointers to refer to objects, which is what a normal object reference does in Julia, except without the ability to segfault your program.

6 Likes

mutable structs are heap allocated!

dir = current_dir

does not create a new object but refers to the exact same object!

julia> mutable struct A; x::Int; end

julia> a = A(42)
A(42)

julia> b = a
A(42)

julia> b.x = 43
43

julia> a
A(43)
4 Likes

amusing
How come I never realized it