#Prevent copy of struct

1 messages · Page 1 of 1 (latest)

sudden sonnet
#

I would like to make a struct non-copyable, but I cannot seem to get how to do that in Zig (if possible). I had a look at proposals about "pinned" and other related concepts, but nothing made it in the language as far as I know. What would be a good approach?

Note: to prevent some xy problem situation, what I'm really trying to do is follow a "building a debugger" book which uses c++. At some point, you create a simple struct representing a process, with a single field for its pid. And you don't want any instance of it to be copied. In c++ you would for instance set all constructors as private and require the user to create unique_ptr of your object. What do you recommand in Zig?

trim parrot
#

you can gate the variable behind *anyopaque

#

or you can have a singleton to make sure there's only 1 instance of it

#

but basically, there's no straightforward way to have a non-copyable type in Zig

iron ocean
#

Yeah you cant prevent it in zig, opaque is the only way

#

Opaque can have declarations so its not that bad

sudden sonnet
iron ocean
#

It does require the opaque to be allocated though

trim parrot
#

but do consider if it's worth all the trouble, since in Zig, you just kinda don't copy it if you don't want to

#

even the standard library has problems with this all the time, so it's very much an unsolved (won't be solved?) problem in Zig

sudden sonnet
#

So my field would be pid: *anyopaque and I allocate a pid_t in the init() method? What makes it work, copying anyopaque is a comptime error because we don't knew its size or something like that?

sudden sonnet
trim parrot
#
const ProcessImpl = struct {
    pid: u16,
};
const Process = opaque {
    fn init(allocator: std.mem.Allocator) error{OutOfMemory}!*Process {
        // Allocate `ProcessImpl`
        return @ptrCast(try allocator.create(ProcessImpl));
    }

    fn pid(self: *const Process) u16 {
        return @as(*const ProcessImpl, @ptrCast(@alignCast(self))).pid;
    }
};

pub fn main() !void {
    var backingAllocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
    defer _ = backingAllocator.detectLeaks();

    const gpa: std.mem.Allocator = backingAllocator.allocator();

    const o: *Process = try .init(gpa);
    std.debug.print("{}\n", .{o.pid()});
}

const std = @import("std");
trim parrot
small stirrup
#

My current approach with Zig code is "don't treat any struct as copyable" by default. Before I decide to copy any struct, I check the struct itself, whether it's supposed to be copyable or not.
With some structs (even in the std) it can be a bit tricky to figure out the design intention, even if the struct is technically copyable, who knows if the updated impl will break the copyability. But most of the time it works.

trim parrot
#

this is why i hope there's a linter for Zig to deal with this kind of thing

small stirrup
#

I'm more looking forward to the pinned feature

trim parrot
#

so that we can have sth like

// no-copy
const Process = struct {
  ...
};
sudden sonnet
#

Thanks, that makes it way clearer! I'm not very familiar with opaque, but I'll have a look myself. 🙂

iron ocean
#
var thing: Thing = undefined;
thing.init();

Is common in zig code as well fyi

#

Just saying that allocator to create opaque struct may not be worth it

#

I feel like zig may get some sort of "non copyable" thing in future as Io Writer/Reader interfaces has the same restriction

trim parrot
#

you can also have sth like

fn ptr(self: anytype) blk: {
  const info = @typeInfo(@TypeOf(self));
  break :blk if(info.pointer.is_const) *const ProcessImpl else *ProcessImpl;
} {
  return @ptrCast(@alignCast(self));
}

in Process to make accessing the underlying pointer easier

iron ocean
#

Even with such feature, i feel like zig should allow force a copy if programmer so wants

#

Its all memory in the end after all

trim parrot
iron ocean
#

For opaque you need allocator obviously

#

Or store the state somewhere else

trim parrot
#

oh, i thought it had something to do with preventing copying of the struct, i misunderstood

small stirrup
fathom bloom
#

You can stick it behind a vtable so the user doesn't have direct access to the struct. Beyond that I'm not sure how you would gate that behavior. There other option is runtime checking and verify that the struct is in the same place. Store the address of the struct and omit the code on release.

warm radish
#

I don't understand what defect you're preventing by disallowing copying. It's just a handle/id.

iron ocean
#

Ah, I guess you meant the pid? In which case you are right, there's no need to prevent copying

sudden sonnet
#

Well, its a pid plus some info (the status of the process) and some utility functions (like continuing or killing the process). So holding such an instance of Process means having "ownership" of the process. I don't want a copy of the instance killing the process while the original still expects the process to be debugged for example.

trim parrot
#

you should avoid thinking about ownership like that in Zig, my mental model is the object that owns the resource is the one that calls .deinit()

#

so essentially, all objects are views

sudden sonnet
#

Well, they are views but they have all the rights on it. Here, by "ownership", I was not talking about memory (as there is no allocation in my case), but more about a higher level concept (if you have an instance of Process, you know you are the only one messing with it). Would you say than even in this case it is not relevant to think that way?

trim parrot
#

yup, consider ArrayList, you can always make a copy of it, in fact, you can always pass it by value, the copy holds a view to the original resource anyway, it’s basically a pinky swear that it doesn’t deallocate the underlying resource.

#

that’s just how Zig works, and if you’re uncomfortable with that, you’ll being fighting the language instead of using it

sudden sonnet
#

Ok, that helps a lot, thanks. 🙂

gray sage
# sudden sonnet Well, they are views but they have all the rights on it. Here, by "ownership", I...

C++ has possiblity to disable ol copy/move ctors/operators
When I started to develop new class - immediately used this option.

Go also has simular functionality:

// A WaitGroup must not be copied after first use.

type WaitGroup struct {

    noCopy noCopy   <========


    // Bits (high to low):

    //   bits[0:32]  counter

    //   bits[32]    flag: synctest bubble membership

    //   bits[33:64] wait count

    state atomic.Uint64

    sema  uint32

}

Raise this problem in other forums and check github zig issues
Possibly it was already discussed

Zig is relative young language , possibly this feature may be added

vernal loom
fathom bloom
sonic sparrow
#

If you want to loosely prevent something from being accessible, module boundaries are the only real mechanism in zig (e.g. just give the user a handle, and invoke your module's public functions with that handle). Zig isn't trying to support object oriented programming, and structs are intentionally just bundles of data.

If you are trying to secure your program against itself within the same process, you are going to fail. Zig and C++ cannot prevent someone from spoofing access to the memory and copying out whatever they want, or just going ahead and invoking whatever function via their own prototype.