2 pointsby birdculture6 hours ago1 comment
  • _rend6 hours ago
    Spoiler:

    Yeah, this is an unfortunate problem in most non-GC languages with implicit destructors. See, e.g., Swift

        enum Node {
            case none
            indirect case next(Node)
        }
        
        func example(_ n: Int) {
            var head = Node.none
            for _ in 0 ..< n {
                head = .next(head)
            }
        }
        
        example(100000)
        // Terminated due to signal: SEGMENTATION FAULT (11)
    
    or Rust

        struct Node {
            next: Option<Box<Node>>
        }
        
        impl Node {
            fn new(next: Node) -> Self {
                Node { next: Some(Box::new(next)) }
            }
        }
        
        fn example(n: isize) {
            let mut head = Node { next: None };
            for _ in 0 .. n {
                head = Node::new(head);
            }
        }
        
        fn main() {
            example(100000)
            // thread 'main' (24909708) has overflowed its stack
            // fatal runtime error: stack overflow, aborting
            // Terminated due to signal: ABORT TRAP (6)
        }
    
    I'm not aware of languages (esp. systems languages) that can either guarantee some form of tail-call optimization for destructors, or that make an attempt to recognize recursive data structures and accommodate them in some way. (The latter is likely a losing proposition, anyway.)