LinearType and ContextManager

Hello,
i’d like to share an way to create an context-manager that uses LinearTypes.
Also would appreciate any feedbacks from team on this.
(ContextManager and requiring __copyinit__ or not)

This struct greatly improved the API of an UI i’m working on (by a lot),
while retaining all the benefits that LinearTypes brings.

Example with emojis:


#✏️ Note: the struct have no `Movable` `Copyable` !

struct MoveCursor[
    O:MutableOrigin=MutableOrigin.empty,
    border:StyledBorder = __StyleBorderNone,
    border_color: Fg= Fg.default
]():
    var ui: Pointer[UI, O]
    var m: StartedMeasurment[O]   #LinearType ⚪
    var after: Bool #Switcher 🔀
    var storage_border: Border[O] #LinearType ⬜

    @staticmethod
    fn AfterThis[border:StyledBorder = __StyleBorderNone, border_color: Fg= Fg.default](
        mut ui:UI, 
        out ret: MoveCursor[__origin_of(ui), border, border_color]
    ):
        ret = __type_of(ret)(ui)
        ret.after=True #Set switch to after ➡️
    
    @staticmethod
    fn BelowThis[border:StyledBorder = __StyleBorderNone, border_color: Fg= Fg.default](
        mut ui:UI, 
        out ret: MoveCursor[__origin_of(ui), border, border_color]
    ):
        ret = __type_of(ret)(ui)
        ret.after=False #Set switch to below ⬇️
    
    fn __init__(out self, ref[O]ui:UI):
        self.ui = Pointer(to=ui)
        self.m = ui.start_measuring()
        self.after = False #Default switch to ➡️
        @parameter
        if Self.__has_border[border]():
            #Initialize LinearType 🟩 for using it !
            self.storage_border = Border[O](self.ui[])
        else:
            #MarkInitialize LinearType 🟦 for __disable_del() later
            #Line is not here to not encourage unsafe things
            #Users can just initialize the type with an "Do nothing" parameter
    
    @staticmethod
    fn __has_border[b:StyledBorder]()->Bool:
        #If type is "none", return False (types-state)
        return not _type_is_eq[b, __StyleBorderNone]()

    fn __enter__(mut self): ... #Nothing here, struct uses `__init__`
    fn __exit__(mut self): ... #Nothing here, struct uses `__del__`
    
    fn __del__(owned self):
        @parameter
        if Self.__has_border[border]():
            #Consume 1 🟩 with struct method that does something
            self.storage_border^.end_border[border](self.ui[], border_color)
        else:
            #Consume 2 🟦 with struct method that __disable_del
            self.storage_border^.__unsafe_del()
        
        # ⚪ Consume type into another LinearType (`CompletedMeasurement`)
        var tmp = self.m^.stop_measuring() 
        if self.after: #Check the switch 🔀
            tmp^.move_cursor_after() #Consume 1 🟢
        else:
            tmp^.move_cursor_below() #Consume 2 🔵

The result is this API:

with MoveCursor.BelowThis[StyledBorderSimple](ui): 
    ...
with MoveCursor.BelowThis[StyledBorderSimple, Fg.magenta](ui):
    ...
with MoveCursor.BelowThis(ui): 
    ...
with MoveCursor.AfterThis[StyledBorderDouble](ui): 
    ...
with MoveCursor.AfterThis[StyledBorderDouble, Fg.magenta](ui): 
    ...
with MoveCursor.AfterThis(ui): 
    ...

And compared to the wrapped API:

var m = ui.start_measuring()
var b = m.start_border() #b now have origin of m
"Hello world" in ui
b^.end_border[StyledBorderSimple, Fg.blue]() #b end before m
var m_stop = m^.stop_measuring()
m_stop^.move_cursor_below() #all measured, can move draw cursor!

Complete example:

Your MovCursor isn’t annotated to be a Linear type, so instances of it will be deallocated even if they’re not used - breaking the only rule of Linearity: Types MUST be used exactly once.

Good thinking, linear types are used only once, and wait, here is the twist :smiley:

Type instances must be consumed only once, and in an specific way,
either automatically (like this custom __del__, called by asap), or manually :+1:

This __del__ handles two ways of consuming one type for example.
(Does not leave any of the instances stored in any fields unconsumed)

Because the __del__ knows how to dispose of the types, in all branches,
asap can do the cleanup, so i have to think this is correct.

One cool thing is that this is an ui,
so if there was a bug, the ui screen would look like mr potato from the movies lol.

In the next example, if __del__ is removed/commented,
mojo would have say, hey this lineartype is not del,
let’s not build this, which is great:

@explicit_destroy
struct MyLinear:
    var x: Int
    fn __init__(out self): self.x=1
    fn remove(owned self): 
        __disable_del(self)

struct NotLinear: #⬅️ Not an linear type
    var val: MyLinear
    fn __init__(out self): self.val=MyLinear()
    fn __del__(owned self): self.val^.remove() #⬅️ Try comment this

fn main():
    var value = NotLinear()
    print(value.val.x)

Sorry if this is a long response,
i could be wrong and i appreciate your thinking @melodyogonna ! :handshake:
(please double check this)

I’m thinking about what happens if I initialize movecursor without doing anything with it, like this:

fn main():
    var cursor = MoveCursor()

Your __del__ assumes I will always call belowThis or AfterThis, but nothing compels me to, so your logic has a soundness problem.

Huge thanks, yes that is an very valid observation, and it comes with a new feature!
on a side note, you could not break the ui by going ahead and doing this:
(but humbly it is work in progress, so it is far from ‘bug-free’)

var cursor = MoveCursor(ui)
"hello" in ui
"dear" in ui
cursor^.__del__()
"world" in ui
"Hello" in ui
for _ in range(100): MoveCursor(ui)
"World" in ui
# output:
# Hello
# World

It would move the cursor below, which is the default ui behaviour anyway when not measuring :smiley:
(Also what happens when the measured area is [0,0] (width, height))

:right_arrow: Again thanks for this lol, it means we could make it movable for recursivity too and enter when needed!

I’d like to not entangle the ui too much in this thread tho,
because this thread is really about LinearType and ContextManager,
but that is really interesting, would you like to dive in the repo together sometime !?