惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Project Zero
Project Zero
月光博客
月光博客
Y
Y Combinator Blog
T
The Blog of Author Tim Ferriss
O
OpenAI News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Know Your Adversary
Know Your Adversary
Last Week in AI
Last Week in AI
S
Securelist
Engineering at Meta
Engineering at Meta
博客园 - 司徒正美
P
Privacy & Cybersecurity Law Blog
T
Tailwind CSS Blog
F
Fortinet All Blogs
博客园 - 三生石上(FineUI控件)
Scott Helme
Scott Helme
MyScale Blog
MyScale Blog
P
Proofpoint News Feed
云风的 BLOG
云风的 BLOG
C
Cisco Blogs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
小众软件
小众软件
U
Unit 42
Microsoft Azure Blog
Microsoft Azure Blog
Hacker News: Ask HN
Hacker News: Ask HN
Hugging Face - Blog
Hugging Face - Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
SecWiki News
SecWiki News
宝玉的分享
宝玉的分享
P
Proofpoint News Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
Lohrmann on Cybersecurity
IT之家
IT之家
Security Archives - TechRepublic
Security Archives - TechRepublic
I
InfoQ
S
Security @ Cisco Blogs
Webroot Blog
Webroot Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
F
Full Disclosure
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The GitHub Blog
The GitHub Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Jina AI
Jina AI
Cyberwarzone
Cyberwarzone
人人都是产品经理
人人都是产品经理
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
B
Blog RSS Feed
Apple Machine Learning Research
Apple Machine Learning Research

Modular Blog

Qualcomm to Acquire Modular Modular 26.4: SOTA MoE Serving, Model Bringup via Agent Skills, Mojo 1.0 Beta 2 and More ModCon 2026: Modular’s Developer Conference Day Zero: MiniMax M3 Open Weights on Modular Cloud Modverse #55: Mojo 1.0 Beta, Community Mojo Libraries, and Real-Time Patient Conversations Powered by MAX What about OpenCL and CUDA C++ alternatives? (Democratizing AI Compute, Part 5) Why LLM Inference Needs a New Kind of Router - Part 3 Three trends from MLSys 2026 Why LLM Inference Needs a New Kind of Router - Part 2 How I built a pure Mojo app (and 10 libraries) with AI agents Hippocratic AI partners with Modular to power flexible, high-quality inference for real-time patient conversations Translating to Mojo via AI Agents Inkwell: Why Your Inference Platform Matters As Much As Your Model Why LLM Inference Needs a New Kind of Router - Part 1 Modular 26.3: Mojo 1.0 Beta, MAX Video Gen, and more Modverse #54: AMD AI DevDay, New Modular Offices, and a Community That Keeps Shipping How Frontier Coding Agents Built a Video Diffusion Pipeline on MAX TileTensor Part 1 - Safer, More Efficient GPU Kernels Modular Opens Edinburgh & San Francisco Offices Structured Mojo Kernels Part 4 - Portability and the Road Ahead Day Zero Launch: Fastest Performance for Gemma 4 on NVIDIA and AMD Modverse #54: From GTC to Edinburgh, a Community Building Momentum Software Pipelining for GPU Kernels: Part 1 - The Pipeline Problem Structured Mojo Kernels Part 3 - Composition in Practice Modular 26.2: State-of-the-Art Image Generation and Upgraded AI Coding with Mojo Modular at NVIDIA GTC 2026: MAX on Blackwell, Mojo Kernel Porting, and DeepSeek V3 on B200 Structured Mojo Kernels Part 2 - The Three Pillars Modverse #53: Community Builds, Research Milestones, and a Growing Ecosystem Structured Mojo Kernels Part 1 - Peak Performance, Half the Code The Claude C Compiler: What It Reveals About the Future of Software BentoML Joins Modular The Five Eras of KVCache Modular 26.1: A Big Step Towards More Programmable and Portable AI Infrastructure How to Beat Unsloth's CUDA Kernel Using Mojo—With Zero GPU Experience 🔥 Modular 2025 Year in Review The path to Mojo 1.0 Modverse #52: Advancing AI Together — Community Projects & Platform Milestones Modular 25.7: Faster Inference, Safer GPU Programming, and a More Unified Developer Experience "TTS 1 Max" (powered by Modular Platform) Ranked #1 Speech Model on Artificial Analysis PyTorch and LLVM in 2025 — Keeping up With AI Innovation Achieving State-of-the-Art Performance on AMD MI355 — in Just 14 Days Modular Raises $250M to scale AI's Unified Compute Layer Modular 25.6: Unifying the latest GPUs from NVIDIA, AMD, and Apple Matrix Multiplication on Blackwell: Part 4 - Breaking SOTA Modverse #51: Modular x Inworld x Oracle, Modular Meetup Recap and Community Projects Matrix Multiplication on Blackwell: Part 3 - The Optimizations Behind 85% of SOTA Performance Matrix Multiplication on Blackwell: Part 2 - Using Hardware Features to Optimize Matmul Matrix Multiplication on Blackwell: Part 1 - Introduction Modverse #50: Modular Platform 25.5, Community Meetups, and Mojo's Debut in the Stack Overflow Developer Survey Modular Platform 25.5: Introducing Large Scale Batch Inference SF Compute and Modular Partner to Revolutionize AI Inference Economics AI Agents for AWS Marketplace Modverse #49: Modular Platform 25.4, Modular 🤝 AMD, and Modular Hack Weekend Inside Modular Hack Weekend: Top Projects and Community Highlights How is Modular Democratizing AI Compute? (Democratizing AI Compute, Part 11) Modular 25.4: One Container, AMD and NVIDIA GPUs, No Lock-In Introducing Mammoth: Enterprise-Scale GenAI Deployments Made Simple Modular + AMD: Unleashing AI performance on AMD GPUs Modverse #48: Modular Platform 25.3, MAX AI Kernels, and the Modular GPU Kernel Hackathon Exploring Metaprogramming in Mojo Modular GPU Kernel Hackathon Highlights: Innovation, Community, & Mojo🔥 Modular’s bet to break out of the Matrix (Democratizing AI Compute, Part 10) Modular Platform 25.3: 450K+ Lines of Open Source Code and pip Packaging A New, Simpler License for MAX and Mojo Why do HW companies struggle to build AI software? (Democratizing AI Compute, Part 9) Modverse #47: MAX 25.2 and an evening of GPU programming at Modular HQ What about the MLIR compiler infrastructure? (Democratizing AI Compute, Part 8) What about Triton and Python eDSLs? (Democratizing AI Compute, Part 7) MAX 25.2: Unleash the power of your H200's–without CUDA! What about TVM, XLA, and AI compilers? (Democratizing AI Compute, Part 6) Modverse #46: MAX 25.1, MAX Builds, and Democratizing AI Compute CUDA is the incumbent, but is it any good? (Democratizing AI Compute, Part 4) MAX 25.1 - Introducing MAX Builds How did CUDA succeed? (Democratizing AI Compute, Part 3) Paged Attention & Prefix Caching Now Available in MAX Serve What exactly is “CUDA”? (Democratizing AI Compute, Part 2) Modular DeepSeek's Impact on AI (Democratizing AI Compute, Part 1) Modular Hands-on with Mojo 24.6 Evaluating Llama Guard with MAX 24.6 and Hugging Face Modular Introducing MAX 24.6: A GPU Native Generative AI Platform MAX GPU: State of the Art Throughput on a New GenAI platform Understanding SIMD: Infinite Complexity of Trivial Problems Community Spotlight: Writing Mojo with Cursor MAX 24.5 - With SOTA CPU Performance for Llama 3.1 Announcing stack-pr: an open source tool for managing stacked PRs on GitHub Debugging in Mojo🔥 Write hardware-agnostic custom ops for PyTorch | Modular Take control of your AI Develop locally, deploy globally A brief guide to the Mojo n-body example What's new in MAX 24.4? MAX on macOS, fast local Llama3, native quantization and GGUF support What’s new in Mojo 24.4? Improved collections, new traits, os module features and core language enhancements MAX 24.4 - Introducing quantization APIs and MAX on macOS Deep dive into ownership in Mojo What ownership is really about: a mental model approach Fast⚡k-means clustering in Mojo🔥: a guide to porting Python to Mojo🔥 for accelerated k-means clustering
Hands-on with Mojo 24.5
No items found. · 2024-10-01 · via Modular Blog

Mojo 24.5 is here, and it's our biggest release yet. It's available now through the new Magic package manager, and is bundled with the MAX 24.5 release. Please install Magic to follow along.

This release includes several new core language changes and standard library enhancements. In this blog post, we will dive into many of these new features and language improvements using code examples. We focus our examples on the unified pointer type UnsafePointer, and gradually introduce other notable features. By the end of this blog post, you will have a thorough understanding of these enhancements and can comfortably utilize them in your Mojo code. 

One of the biggest highlights of this release is the significant contributions from our community. We received numerous pull requests from 11 community contributors that included new features, bug fixes, documentation enhancements, and code refactoring. Special thanks 🙏🏼 to our community contributors:

@jjvraw, @artemiogr97, @martinvuyk, @jayzhan211, @bgreni, @mzaks, @msaelices, @rd4com, @jiex-liu, @kszucs, @thatstoasty.

For a complete list of changes, please refer to the changelog for version 24.5.

All the code for this blog post is available in our GitHub repository.

Dramatically reduced the auto-imported modules

Mojo v24.5 has significantly reduced the set of automatically imported entities into users' programs. Previously, modules like memory, sys, os, utils, python, bit, random, math, builtin, and collections were automatically available. Now, only the explicitly enumerated entities in prelude/__init__.mojo are automatically imported. This change requires developers to explicitly import the entities they use, preventing unexpected namespace pollution and improving code clarity.

No need for var in fn

Mojo 24.5 has relaxed the requirement of using var within fn functions, making fn more similar to def functions, while still maintaining its unique capabilities. However, fn stills differs from def by allowing greater control over memory, which is particularly important when interfacing with low-level C code through Foreign Function Interface (FFI). Another difference is that def raises implicitly.

Unified pointer data structure via UnsafePointer

In 24.5, we’ve unified the pointer data structures by consolidating all previous pointer types into a single UnsafePointer. The previous types -– DTypePointer, LegacyPointer and Pointer— have been removed. Their functionalities have been incorporated into UnsafePointer, simplifying the pointer system while retaining all necessary capabilities. This unification streamlines pointer usage in Mojo and reduces confusion for developers.

The next section is a refresher on the UnsafePointer and how to properly use it. Please feel free to skip this section and go to the examples.

Refresher on UnsafePointer

Understanding Undefined Behavior (UB) with uninitialized memory

The UnsafePointer type creates an indirect reference to a location in memory. You can use an UnsafePointer to dynamically allocate and free memory, or to point to memory allocated by some other piece of code. You can use these pointers to write code that interacts with low-level interfaces, to interface with other programming languages, or to build certain kinds of data structures. But as the name suggests, they're inherently unsafe. For example, when using unsafe pointers, you're responsible for ensuring that memory gets allocated and freed correctly.

When we allocate memory using UnsafePointer in Mojo, the allocated memory is uninitialized. Accessing this uninitialized memory without proper initialization is considered Undefined Behavior (UB). This can occur when using the __getitem__ operation (i.e., square brackets []) on an uninitialized pointer:

Mojo

ptr = UnsafePointer[Int].alloc(1) ptr[0] = 42 <-- Undefined Behavior

The above example shows ptr[0] attempting to provide a valid reference, but since the memory is uninitialized, the validity and lifetime of the reference is ambiguous. Unlike C, which does not differentiate and has historically been plagued by memory vulnerabilities due to this, Mojo requires explicit initialization of unsafe pointers to ensure memory safety.

Proper Initialization with init_pointee_copy/move

To ensure that memory is properly initialized, Mojo provides methods like init_pointee_copy from the UnsafePointer, suitable for numeric types such as Int. This method allows for the safe initialization of memory which we should use to initialize ptr safely.

Mojo

ptr = UnsafePointer[Int].alloc(1) ptr.init_pointee_copy(42)

In the provided code snippet, initializing ptr with ptr[0] = 42 was identified as problematic due to it involving direct assignment to uninitialized memory. Using init_pointee_copy resolves this issue by ensuring that the memory is correctly and safely initialized before use.

You can read a more extensive discussion of how to allocate and initialize pointers in the UnsafePointer documentation.

With this primer, we will start using UnsafePointer and gradually introduce other notable features of Mojo 24.5, so that we can see end-to-end examples incorporating all the new features.

Example 1: UnsafeBuffer

The following is a way to implement a buffer data structure in Mojo using the UnsafePointer. Can you spot why this buffer is unsafe?

Mojo

from memory import memset_zero struct UnsafeBuffer:    var data: UnsafePointer[UInt8]    var size: Int    fn __init__(inout self, size: Int):        self.data = UnsafePointer[UInt8].alloc(size)        memset_zero(self.data, size)        self.size = size    fn __del__(owned self):        self.data.free()

If we run magic run mojo unsafe_buffer.mojo:

Mojo

def main():    ub = UnsafeBuffer(10)    print("initial value at index 0:")    print(ub.data[0])    ub.data[0] = 255    print("value at index 0 after getting set to 255:")    print(ub.data[0])

We get the output:

Output

initial value at index 0: 0 value at index 0 after getting set to 255: 0

But why? What went wrong?

As stated in the safety partUnsafePointer does not carry any lifetime information, which means that the lifetime of data in UnsafeBuffer is not connected to the lifetime of the ub instance. In the last print(ub.data[0]), the compiler considers ub as unused immediately after the pointer to the element is calculated by ub.data[0], but before reading from that pointer. As a result, Mojo’s (ASAP) destructor gets activated, freeing the data pointer. Any subsequent call becomes Undefined Behavior (UB) because they are accessing freed memory. This is why, quite luckily, we see the unexpected output.

In the next example, we try to make our buffer implementation safe.

Example 2: SafeBuffer

The following implementation ties the lifetime of (private) _data pointer to an instance of a SafeBuffer via the write and read methods. Moreover, we are using debug_assert to check the bounds in write and read methods which gets activated using the compiler option -D MOJO_ENABLE_ASSERTIONS. We will shortly see how.

Mojo

from memory import memset_zero struct SafeBuffer:   var _data: UnsafePointer[UInt8]   var size: Int   fn __init__(inout self, size: Int):       debug_assert(size > 0, "size must be greater than zero")       self._data = UnsafePointer[UInt8].alloc(size)       memset_zero(self._data, size)       self.size = size   fn __del__(owned self):       self._data.free()   fn write(inout self, index: Int, value: UInt8):       debug_assert(0 <= index < self.size, "index must be within the buffer")       self._data[index] = value   fn read(self, index: Int) -> UInt8:       debug_assert(0 <= index < self.size, "index must be within the buffer")       return self._data[index]

Now if we run the application code

Mojo

def main():    sb = SafeBuffer(10)    sb.write(0, 255)    print("value at index 0 after getting set to 255:")    print(sb.read(0))

via the following command:

Bash

magic run mojo -D MOJO_ENABLE_ASSERTIONS safe_buffer.mojo

We see the output is as expected:

Output

value at index 0 after getting set to 255: 255

Note that it is crucial to not directly access the _data and instead use the write and read methods in order to tie the lifetime of the _data pointer to the underlying instance.

Named result bindings

Mojo now supports named result bindings. Named result bindings are useful for directly emplacing function results into the output slot of a function. This feature provides more flexibility and guarantees around emplacing the result of a function compared to a "guaranteed" named return value optimization.

To see how it can simplify our application code, let’s have a look at the following code continuing our SafeBuffer:

Mojo

struct SafeBuffer:    ...    @staticmethod    fn initialize_with_value(size: Int, value: UInt8) -> Self as output:        output = SafeBuffer(size)        for i in range(size):            output.write(i, value)        return def main():    buffer = SafeBuffer.initialize_with_value(size=10, value=128)

In this code, we define a static method initialize_with_value that returns an instance of SafeBuffer. The method declares output as the return variable of type Self. Within the method, we initialize output by creating a new SafeBuffer of the specified size. We then fill the buffer by writing the given value to each index.

By using a named return variable output, the compiler constructs and modifies the return value directly in the memory location where it will ultimately reside. This eliminates the need for unnecessary copying or moving of the SafeBuffer instance which is important for types that are not movable and copyable. The as output syntax in the function signature indicates that output is the return variable, and the final return statement returns control without specifying a value, as output is already the return value.

Argument exclusivity verification

Mojo 24.5 now checks (at compile time) that mutable argument references don’t alias other references. That means that Mojo requires references (including implicit references due to borrowed/inout arguments) to be uniquely referenced (non-aliased) if mutable. This is important for code safety, because it allows the compiler (and readers of code) to understand where and when a value is mutated. It is also useful for performance optimization because it allows the compiler to know that accesses through immutable references cannot change behind the scenes. 

To see how argument exclusivity helps to write safe code and can catch errors at compile time, let’s have a look at the following process_buffers function that uses our SafeBuffer to overwrite a mutable buffer with the values of a borrowed buffer:

Mojo

fn process_buffers(buffer1: SafeBuffer, inout buffer2: SafeBuffer):   debug_assert(buffer1.size == buffer2.size, "buffer sizes much match")   for i in range(buffer1.size):       buffer2.write(i, buffer1.read(i))

Now image that in our application code we have:

Mojo

def main():    buffer1 = SafeBuffer.initialize_with_value(size=10, value=128)    buffer2 = SafeBuffer(10)    process_buffers(buffer1, buffer1)

In our application code, we accidentally pass buffer1 as both arguments to process_buffers, with the second argument intended to be mutable (inout). This means that we're trying to mutate buffer1 while also having a borrowed reference to it. Mojo's argument exclusivity rules enforce that mutable references (inout parameters) must be unique and non-aliased. The compiler detects this violation at compile time and creates a warning for potential bugs and ensuring code safety by enforcing no-aliasing rules. This also allows the compiler to optimize more aggressively by treating argument references as not-aliased.

When running the code, the compiler warns us about such issues:

Output

warning: call argument allows writing a memory location previously readable through another aliased argument    process_buffers(buffer1, buffer1)                   ^~~~~~~~  ~~~~~~~ note: 'buffer1' value is passed through aliasing 'inout' argument    process_buffers(buffer1, buffer1)                   ^         ~~~~~~~

Formattable trait

As of Mojo 24.5, the print function now requires that its arguments conform to the Formattable trait. This enables efficient stream-based writing by default, avoiding unnecessary intermediate String heap allocations.

Let’s use this new trait and implement format_to and __str__ so that we can print our buffer values easily. Everything stays the same, except we include Formattable and Stringable as follows:

Mojo

struct SafeBuffer(Stringable, Formattable):   ...   fn __str__(self) -> String:       return String.format_sequence(self)   fn format_to(self, inout writer: Formatter):      debug_assert(self.size > 0, "size must be greater than zero")       writer.write("[")       for i in range(self.size - 1):           writer.write(self._data[i], ", ")       writer.write(self._data[self.size - 1])       writer.write("]")

Now let’s test it within our application code via magic run mojo safe_buffer.mojo:

Mojo

def main():    buffer1 = SafeBuffer.initialize_with_value(size=10, value=128)    buffer2 = SafeBuffer(10)    process_buffers(buffer1, buffer2)    print("buffer2:", buffer2)

Which produces the expected outputs:

Output

buffer2: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128]

For the record, here is all the code for safe_buffer.mojo:

Mojo

from memory import memset_zero struct SafeBuffer(Stringable, Formattable):    var _data: UnsafePointer[UInt8]    var size: Int    fn __init__(inout self, size: Int):        debug_assert(size > 0, "size must be greater than zero")        self._data = UnsafePointer[UInt8].alloc(size)        memset_zero(self._data, size)        self.size = size    @staticmethod    fn initialize_with_value(size: Int, value: UInt8) -> Self as output:        output = SafeBuffer(size)        for i in range(size):            output.write(i, value)        return    fn __del__(owned self):        self._data.free()    fn write(inout self, index: Int, value: UInt8):        debug_assert(0 <= index < self.size, "index must be within the buffer")        self._data[index] = value    fn read(self, index: Int) -> UInt8:        debug_assert(0 <= index < self.size, "index must be within the buffer")        return self._data[index]    fn __str__(self) -> String:        return String.format_sequence(self)    fn format_to(self, inout writer: Formatter):        debug_assert(self.size > 0, "size must be greater than zero")        writer.write("[")        for i in range(self.size - 1):            writer.write(self._data[i], ", ")        writer.write(self._data[self.size - 1])        writer.write("]") fn process_buffers(buffer1: SafeBuffer, inout buffer2: SafeBuffer):    debug_assert(buffer1.size == buffer2.size, "buffer sizes much match")    for i in range(buffer1.size):        buffer2.write(i, buffer1.read(i)) def main():    sb = SafeBuffer(10)    sb.write(0, 255)    print("safe buffer outputs:")    print(sb.read(0))    buffer1 = SafeBuffer.initialize_with_value(size=10, value=128)    buffer2 = SafeBuffer(10)    # process_buffers(buffer1, buffer1) # <-- argument exclusivity detects such errors at compile time    process_buffers(buffer1, buffer2)    print("buffer2:", buffer2)

Example 3: Generic SafeBuffer[T]

In this example, we aim to make our SafeBuffer implementation generic, allowing it to handle different data types. To achieve this, we use UnsafePointer with Optional[T] so that we can initialize our pointer with NoneType. By making SafeBuffer generic, we can create buffers for various data types while maintaining safety and proper initialization.

Mojo

from collections import Optional struct SafeBuffer[T: CollectionElement]:   var _data: UnsafePointer[Optional[T]]   var size: Int   fn __init__(inout self, size: Int):       debug_assert(size > 0, "size must be greater than zero")       self._data = UnsafePointer[Optional[T]].alloc(size)       for i in range(size):           (self.data + i).init_pointee_copy(NoneType())       self.size = size   @staticmethod   fn initialize_with_value(size: Int, value: T) -> Self as output:       output = SafeBuffer[T](size)       for i in range(size):           output.write(i, value)       return   fn __copyinit__(inout self, existing: Self):       self._data = existing._data       self.size = existing.size   fn __moveinit__(inout self, owned existing: Self):       self._data = existing._data       self.size = existing.size   fn __del__(owned self):       self._data.free()   fn write(inout self, index: Int, value: Optional[T]):       debug_assert(0 <= index < self.size, "index must be within the buffer")       self._data[index] = value   fn read(self, index: Int) -> Optional[T]:       debug_assert(0 <= index < self.size, "index must be within the buffer")       return self._data[index]     fn take(inout self, index: Int) -> Optional[T] as output:        output = self.read(index)        self.write(index, Optional[T](None))        return

Conditional conformance

Mojo 24.5 allows types to “conditionally conform” to traits, which allows a generic struct to conform to a trait only when its type parameters meet certain conditions. In our case, we can make SafeBuffer[T: CollectionElement] conform to Stringable as long as T itself conforms to Stringable (i.e. T: Stringable). The key requirement for conditional conformance is that the trait used in the generic parameter must include the trait(s) used in the struct definition.

To achieve this, we use the StringableFormattableCollectionElement that is defined to include both Formattable and StringableCollectionElement traits:

Mojo

trait StringableFormattableCollectionElement(Formattable, StringableCollectionElement):   ...

We then define the __str__ method with a type parameter U that must conform to StringableFormattableCollectionElement:

Mojo

fn __str__[U: StringableFormattableCollectionElement](self: SafeBuffer[U]) -> String:    ...

Note that the self parameter is typed as SafeBuffer[U], repeating the type parameter U. This ensures that the __str__ method is only available when U conforms to the necessary traits, allowing the compiler to catch type errors at compile time if we attempt to use SafeBuffer with a type that does not meet the requirements.

And now we are ready to include such trait in __str__ and format_to by minimally adjusting the code as follows:

Mojo

struct SafeBuffer[T: CollectionElement]:   ...   fn __str__[U: StringableFormattableCollectionElement](self: SafeBuffer[U]) -> String:       ret = String()       writer = ret._unsafe_to_formatter()       self.format_to(writer)       _ = writer^       return ret^   fn format_to[       U: StringableFormattableCollectionElement   ](self: SafeBuffer[U], inout writer: Formatter):       debug_assert(self.size > 0, "size must be greater than zero")       writer.write("[")       for i in range(self.size - 1):           if self._data[i]:               writer.write(self._data[i].value(), ", ")           else:               writer.write("None", ", ")       if self._data[self.size - 1]:           writer.write(self._data[self.size - 1].value())       else:           writer.write("None")       writer.write("]")

To test how conditional conformance can catch type errors at compile time, let’s define a dummy type that is neither Stringable nor Formattable, yet still includes CollectionElement requirements:

Mojo

struct NotStringableNorFormattable(CollectionElement):   fn __init__(inout self): ...   fn __copyinit__(inout self, existing: Self): ...   fn __moveinit__(inout self, owned existing: Self): ...

If we try to use it in our application code:

Mojo

def main():    buf = SafeBuffer[NotStringableNorFormattable](10)    buf.__str__()

We get the following compile time error indicating that the type does not conform to the required trait StringableFormattableCollectionElement:

Output

error: invalid call to '__str__': could not deduce parameter 'U' of callee '__str__'   buf.__str__()   ~~~~~~~~~~~^~ note: failed to infer parameter 'U', argument type 'NotStringableNorFormattable' does not conform to trait 'StringableFormattableCollectionElement'       buf.__str__()       ^~~ note: function declared here       fn __str__[U: StringableFormattableCollectionElement](self: SafeBuffer[U]) -> String:       ^

mojo test

Mojo 24.5 also comes with the mojo test option that uses the Mojo compiler for running unit tests. Let’s add a test case under test_generic_safe_buffer.mojo with the following:

Mojo

from testing import assert_equal from generic_safe_buffer import SafeBuffer def test_buffer():   buffer = SafeBuffer[String].initialize_with_value(size=5, value=String("hi"))   val = buffer.take(2).value()   assert_equal(val, String("hi"))   assert_equal(buffer.__str__(), "[hi, hi, None, hi, hi]")

Now we can run magic run mojo test test_generic_safe_buffer.mojo and can see the output as:

Output

Testing Time: 1.828s Total Discovered Tests: 1 Passed : 1 (100.00%) Failed : 0 (0.00%) Skipped: 0 (0.00%)

Magic tip: we can include the following in the tasks section of mojoproject.toml:

mojoproject.toml

[tasks] test = "mojo test test_*.mojo

So that running all the tests becomes as easy as running magic run test.

For completeness, the following includes the entire code for generic_safe_buffer.mojo which we can run via magic run mojo generic_safe_buffer.mojo:

Mojo

from collections import Optional trait StringableFormattableCollectionElement(Formattable, StringableCollectionElement):    ... struct SafeBuffer[T: CollectionElement]:    var _data: UnsafePointer[Optional[T]]    var size: Int    fn __init__(inout self, size: Int):        debug_assert(size > 0, "size must be greater than zero")        self._data = UnsafePointer[Optional[T]].alloc(size)        for i in range(size):            (self._data + i).init_pointee_copy(NoneType())        self.size = size    @staticmethod    fn initialize_with_value(size: Int, value: T) -> Self as output:        output = SafeBuffer[T](size)        for i in range(size):            output.write(i, value)        return    fn __copyinit__(inout self, existing: Self):        self._data = existing._data        self.size = existing.size    fn __moveinit__(inout self, owned existing: Self):        self._data = existing._data        self.size = existing.size    fn __del__(owned self):        self._data.free()    fn write(inout self, index: Int, value: Optional[T]):        debug_assert(0 <= index < self.size, "index must be within the buffer")        self._data[index] = value    fn read(self, index: Int) -> Optional[T]:        debug_assert(0 <= index < self.size, "index must be within the buffer")        return self._data[index]    fn __str__[U: StringableFormattableCollectionElement](self: SafeBuffer[U]) -> String:        ret = String()        writer = ret._unsafe_to_formatter()        self.format_to(writer)        _ = writer^        return ret^    fn format_to[        U: StringableFormattableCollectionElement    ](self: SafeBuffer[U], inout writer: Formatter):        debug_assert(self.size > 0, "size must be greater than zero")        writer.write("[")        for i in range(self.size - 1):            if self._data[i]:                writer.write(self._data[i].value(), ", ")            else:                writer.write("None", ", ")        if self._data[self.size - 1]:            writer.write(self._data[self.size - 1].value())        else:            writer.write("None")        writer.write("]")    fn take(inout self, index: Int) -> Optional[T] as output:        output = self.read(index)        self.write(index, Optional[T](None))        return fn process_buffers[T: CollectionElement](buffer1: SafeBuffer[T], inout buffer2: SafeBuffer[T]):    debug_assert(buffer1.size == buffer2.size, "buffer sizes much match")    for i in range(buffer1.size):        buffer2.write(i, buffer1.read(i)) struct NotStringableNorFormattable(CollectionElement):    fn __init__(inout self):        ...    fn __copyinit__(inout self, existing: Self):        ...    fn __moveinit__(inout self, owned existing: Self):        ... def main():    buffer1 = SafeBuffer[UInt8].initialize_with_value(size=10, value=UInt8(128))    buffer2 = SafeBuffer[UInt8](size=10)    # process_buffers(buffer1, buffer1) # <-- argument exclusivity detects such errors at compile time    process_buffers(buffer1, buffer2)    # testing conditional conformance    print(buffer2.__str__())    print(buffer2.take(0).value())    print(buffer2.__str__())    sbuffer1 = SafeBuffer[String].initialize_with_value(size=10, value=String("hi"))    print(sbuffer1.take(5).value())    print(sbuffer1.__str__())    ## uncomment to see the compiler error:    # buf = SafeBuffer[NotStringableNorFormattable](10)    # buf.__str__()

Summary

In this blog post, we explored several new features introduced in Mojo 24.5, including the unified UnsafePointer type, relaxed variable declaration in fn functions, named result bindings, argument exclusivity, and conditional conformance.

We began by discussing the dramatic reduction of auto-imported modules, which improves code clarity by requiring explicit imports. We then dived into the unification of pointer data structures into UnsafePointer, simplifying pointer usage while retaining essential functionalities.

We examined how to properly use UnsafePointer, highlighting the importance of initializing allocated memory to avoid Undefined Behavior. Through the UnsafeBuffer example, we demonstrated the potential pitfalls of accessing UnsafePointer directly.

By introducing the SafeBuffer example, we showcased how to use lifetimes of the underlying UnsafePointer can be tied to an instance of SafeBuffer through the write and read methods, preventing premature deallocation and Undefined Behavior.

We explored the benefits of named result bindings showing how it allows for efficient construction and returning of objects without unnecessary copying or moving.

We discussed argument exclusivity, emphasizing how Mojo's compile-time checks prevent aliasing of mutable references, enhancing code safety and correctness.

We incorporated the Formattable trait to enable efficient and flexible string formatting of our SafeBuffer, and demonstrated how to implement the format_to and __str__ methods.

Finally, we extended our SafeBuffer implementation to be generic, leveraging conditional conformance to ensure that methods like __str__ are only available when the type parameter meets certain trait constraints. This allows the compiler to enforce type safety and catch errors at compile time.

Overall, Mojo 24.5 brings significant enhancements that improve code safety, performance, and developer experience. We encourage you to explore these new features and consider how they can benefit your own projects. For more details, please check out the changelog for Mojo 24.5.

What’s next?

Now that you've learned about the latest features in Mojo 24.5, it's time to put this knowledge into practice and dive deeper into the Mojo ecosystem. Here are some resources and next steps to help you get started and stay connected:

Until next time! 🔥